feat(group): refactor group invite workflow for improved user experience
- Simplify InviteMembers to always send invite card directly to invitee - Modify RespondInvite to check group join type (anyone vs need approval) before adding member - Update SetGroupAddRequest to properly handle invite flow with invitee agreement and admin approval stages - Add case-insensitive search using LOWER() in user repository - Add random offset (0-50) for first page of recommended posts - Update dependencies: remove gorilla/websocket, promote gomail to direct dependency
This commit is contained in:
@@ -649,7 +649,6 @@ func (s *groupService) InviteMembers(userID string, groupID string, memberIDs []
|
||||
if !isInviterMember {
|
||||
return ErrNotGroupMember
|
||||
}
|
||||
isInviterAdmin := s.IsGroupAdmin(userID, groupID)
|
||||
|
||||
inviter, _ := s.userRepo.GetByID(userID)
|
||||
inviterName := "群成员"
|
||||
@@ -685,7 +684,7 @@ func (s *groupService) InviteMembers(userID string, groupID string, memberIDs []
|
||||
|
||||
if _, err := s.requestRepo.GetPendingByGroupAndTarget(groupID, memberID, model.GroupJoinRequestTypeInvite); err == nil {
|
||||
continue
|
||||
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
continue
|
||||
}
|
||||
expireAt := time.Now().Add(72 * time.Hour)
|
||||
@@ -702,42 +701,9 @@ func (s *groupService) InviteMembers(userID string, groupID string, memberIDs []
|
||||
continue
|
||||
}
|
||||
|
||||
if isInviterAdmin {
|
||||
// 群主/管理员邀请:直接发送邀请卡片,等待被邀请人确认
|
||||
s.sendGroupInviteToTarget(group, req, inviterName, inviterAvatar)
|
||||
createdCount++
|
||||
continue
|
||||
}
|
||||
|
||||
inviteeName := "用户"
|
||||
inviteeAvatar := ""
|
||||
if invitee, e := s.userRepo.GetByID(memberID); e == nil && invitee != nil {
|
||||
if invitee.Nickname != "" {
|
||||
inviteeName = invitee.Nickname
|
||||
}
|
||||
inviteeAvatar = invitee.Avatar
|
||||
}
|
||||
reviewerIDs := s.collectGroupReviewerIDs(group.ID, group.OwnerID, userID)
|
||||
for _, reviewerID := range reviewerIDs {
|
||||
s.createSystemNotification(reviewerID, model.SysNotifyGroupJoinApply, inviterName+" 邀请 "+inviteeName+" 加入群聊 "+group.Name+",请审批", &model.SystemNotificationExtra{
|
||||
ActorIDStr: userID,
|
||||
ActorName: inviterName,
|
||||
AvatarURL: inviterAvatar,
|
||||
TargetID: req.Flag,
|
||||
TargetTitle: group.Name,
|
||||
TargetType: string(model.GroupJoinRequestTypeInvite),
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
GroupAvatar: group.Avatar,
|
||||
GroupDescription: group.Description,
|
||||
Flag: req.Flag,
|
||||
RequestType: string(req.RequestType),
|
||||
RequestStatus: string(req.Status),
|
||||
TargetUserID: memberID,
|
||||
TargetUserName: inviteeName,
|
||||
TargetUserAvatar: inviteeAvatar,
|
||||
})
|
||||
}
|
||||
// 无论是否需要审批,都先发送邀请卡片给被邀请人
|
||||
// 被邀请人同意后,再根据群的策略决定是否需要管理员审批
|
||||
s.sendGroupInviteToTarget(group, req, inviterName, inviterAvatar)
|
||||
createdCount++
|
||||
}
|
||||
|
||||
@@ -792,7 +758,7 @@ func (s *groupService) JoinGroup(userID string, groupID string) error {
|
||||
applicantAvatar,
|
||||
)
|
||||
return ErrJoinRequestPending
|
||||
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -861,22 +827,81 @@ func (s *groupService) RespondInvite(userID string, flag string, approve bool, r
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
req.ReviewerID = userID
|
||||
req.ReviewedAt = &now
|
||||
req.Reason = reason
|
||||
|
||||
if approve {
|
||||
if err := s.addMemberToGroupAndConversation(group, userID, userID); err != nil {
|
||||
return err
|
||||
// 检查群的加群策略
|
||||
if group.JoinType == model.JoinTypeAnyone {
|
||||
// 允许任何人加入,直接进群
|
||||
if err := s.addMemberToGroupAndConversation(group, userID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
req.Status = model.GroupJoinRequestStatusAccepted
|
||||
s.createSystemNotification(req.InitiatorID, model.SysNotifySystem, "你发出的群邀请已被接受", &model.SystemNotificationExtra{
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
Flag: req.Flag,
|
||||
RequestType: string(req.RequestType),
|
||||
RequestStatus: string(model.GroupJoinRequestStatusAccepted),
|
||||
})
|
||||
} else {
|
||||
// 需要审批或不允许加入时,被邀请人同意后通知管理员审批
|
||||
// 设置 ReviewerID 为被邀请人表示被邀请人已同意
|
||||
req.ReviewerID = userID
|
||||
req.ReviewedAt = &now
|
||||
|
||||
// 获取被邀请人信息
|
||||
targetUserName := "用户"
|
||||
targetUserAvatar := ""
|
||||
if u, e := s.userRepo.GetByID(userID); e == nil && u != nil {
|
||||
if u.Nickname != "" {
|
||||
targetUserName = u.Nickname
|
||||
}
|
||||
targetUserAvatar = u.Avatar
|
||||
}
|
||||
|
||||
// 获取邀请人信息
|
||||
inviterName := "群成员"
|
||||
inviterAvatar := ""
|
||||
if u, e := s.userRepo.GetByID(req.InitiatorID); e == nil && u != nil {
|
||||
if u.Nickname != "" {
|
||||
inviterName = u.Nickname
|
||||
}
|
||||
inviterAvatar = u.Avatar
|
||||
}
|
||||
|
||||
// 通知管理员审批
|
||||
reviewerIDs := s.collectGroupReviewerIDs(group.ID, group.OwnerID, req.InitiatorID)
|
||||
for _, reviewerID := range reviewerIDs {
|
||||
s.createSystemNotification(reviewerID, model.SysNotifyGroupJoinApply, inviterName+" 邀请 "+targetUserName+" 加入群聊 "+group.Name+",被邀请人已同意,请审批", &model.SystemNotificationExtra{
|
||||
ActorIDStr: req.InitiatorID,
|
||||
ActorName: inviterName,
|
||||
AvatarURL: inviterAvatar,
|
||||
TargetID: req.Flag,
|
||||
TargetTitle: group.Name,
|
||||
TargetType: string(model.GroupJoinRequestTypeInvite),
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
GroupAvatar: group.Avatar,
|
||||
GroupDescription: group.Description,
|
||||
Flag: req.Flag,
|
||||
RequestType: string(req.RequestType),
|
||||
RequestStatus: string(req.Status),
|
||||
TargetUserID: userID,
|
||||
TargetUserName: targetUserName,
|
||||
TargetUserAvatar: targetUserAvatar,
|
||||
})
|
||||
}
|
||||
|
||||
// 通知邀请人:被邀请人已同意,等待管理员审批
|
||||
s.createSystemNotification(req.InitiatorID, model.SysNotifySystem, targetUserName+" 已接受你的群邀请,等待管理员审批", &model.SystemNotificationExtra{
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
Flag: req.Flag,
|
||||
RequestType: string(req.RequestType),
|
||||
RequestStatus: string(req.Status),
|
||||
})
|
||||
}
|
||||
req.Status = model.GroupJoinRequestStatusAccepted
|
||||
s.createSystemNotification(req.InitiatorID, model.SysNotifySystem, "你发出的群邀请已被接受", &model.SystemNotificationExtra{
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
Flag: req.Flag,
|
||||
RequestType: string(req.RequestType),
|
||||
RequestStatus: string(model.GroupJoinRequestStatusAccepted),
|
||||
})
|
||||
} else {
|
||||
req.Status = model.GroupJoinRequestStatusRejected
|
||||
s.createSystemNotification(req.InitiatorID, model.SysNotifySystem, "你发出的群邀请已被拒绝", &model.SystemNotificationExtra{
|
||||
@@ -906,9 +931,18 @@ func (s *groupService) SetGroupAddRequest(userID string, flag string, approve bo
|
||||
if req.Status != model.GroupJoinRequestStatusPending {
|
||||
return ErrGroupRequestHandled
|
||||
}
|
||||
if req.RequestType == model.GroupJoinRequestTypeInvite && req.ReviewerID != "" {
|
||||
// invite 类型中 reviewerID 非空表示已完成管理员审批,等待被邀请人确认
|
||||
return ErrGroupRequestHandled
|
||||
// 检查 invite 类型的请求:
|
||||
// - ReviewerID 非空表示被邀请人已同意,等待管理员审批
|
||||
// - ReviewerID 为空表示被邀请人还未同意,管理员不能提前审批
|
||||
if req.RequestType == model.GroupJoinRequestTypeInvite {
|
||||
if req.ReviewerID == "" {
|
||||
// 被邀请人还未同意,管理员不能提前审批
|
||||
return &ServiceError{Code: 400, Message: "被邀请人尚未同意邀请,无法审批"}
|
||||
}
|
||||
if req.ReviewerID != req.TargetUserID {
|
||||
// ReviewerID 不是被邀请人,说明已经被其他人处理过
|
||||
return ErrGroupRequestHandled
|
||||
}
|
||||
}
|
||||
if !s.IsGroupAdmin(userID, req.GroupID) {
|
||||
return ErrNotGroupAdmin
|
||||
@@ -923,8 +957,7 @@ func (s *groupService) SetGroupAddRequest(userID string, flag string, approve bo
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
req.ReviewerID = userID
|
||||
req.ReviewedAt = &now
|
||||
// 保存管理员ID到ReviewerID(覆盖被邀请人的ID)
|
||||
req.Reason = reason
|
||||
targetUserName := "用户"
|
||||
if u, e := s.userRepo.GetByID(req.TargetUserID); e == nil && u != nil && u.Nickname != "" {
|
||||
@@ -941,29 +974,36 @@ func (s *groupService) SetGroupAddRequest(userID string, flag string, approve bo
|
||||
|
||||
if approve {
|
||||
if req.RequestType == model.GroupJoinRequestTypeInvite {
|
||||
// 管理员审批通过后,不直接拉人;发送邀请卡片等待对方确认
|
||||
inviterName := "群成员"
|
||||
inviterAvatar := ""
|
||||
if u, e := s.userRepo.GetByID(req.InitiatorID); e == nil && u != nil {
|
||||
if u.Nickname != "" {
|
||||
inviterName = u.Nickname
|
||||
}
|
||||
inviterAvatar = u.Avatar
|
||||
// 被邀请人已同意,管理员审批通过后直接进群
|
||||
if err := s.addMemberToGroupAndConversation(group, req.TargetUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
s.sendGroupInviteToTarget(group, req, inviterName, inviterAvatar)
|
||||
s.createSystemNotification(req.InitiatorID, model.SysNotifySystem, "你邀请 "+targetUserName+" 入群已通过审批,等待对方确认", &model.SystemNotificationExtra{
|
||||
req.Status = model.GroupJoinRequestStatusAccepted
|
||||
req.ReviewerID = userID
|
||||
req.ReviewedAt = &now
|
||||
s.createSystemNotification(req.TargetUserID, model.SysNotifyGroupJoinApproved, "你接受的群邀请已通过审批,你已加入群聊 "+group.Name, &model.SystemNotificationExtra{
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
GroupAvatar: group.Avatar,
|
||||
Flag: req.Flag,
|
||||
RequestType: string(req.RequestType),
|
||||
RequestStatus: string(model.GroupJoinRequestStatusPending),
|
||||
RequestStatus: string(model.GroupJoinRequestStatusAccepted),
|
||||
})
|
||||
s.createSystemNotification(req.InitiatorID, model.SysNotifySystem, "你邀请 "+targetUserName+" 入群已通过审批", &model.SystemNotificationExtra{
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
GroupAvatar: group.Avatar,
|
||||
Flag: req.Flag,
|
||||
RequestType: string(req.RequestType),
|
||||
RequestStatus: string(model.GroupJoinRequestStatusAccepted),
|
||||
})
|
||||
} else {
|
||||
if err := s.addMemberToGroupAndConversation(group, req.TargetUserID, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
req.Status = model.GroupJoinRequestStatusAccepted
|
||||
req.ReviewerID = userID
|
||||
req.ReviewedAt = &now
|
||||
s.createSystemNotification(req.TargetUserID, model.SysNotifyGroupJoinApproved, "你申请加入群聊 "+group.Name+" 已通过", &model.SystemNotificationExtra{
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
@@ -996,8 +1036,19 @@ func (s *groupService) SetGroupAddRequest(userID string, flag string, approve bo
|
||||
}
|
||||
} else {
|
||||
req.Status = model.GroupJoinRequestStatusRejected
|
||||
req.ReviewerID = userID
|
||||
req.ReviewedAt = &now
|
||||
if req.RequestType == model.GroupJoinRequestTypeInvite {
|
||||
// 成员邀请被管理员拒绝,仅通知邀请人
|
||||
// 被邀请人已同意但管理员拒绝,通知被邀请人和邀请人
|
||||
s.createSystemNotification(req.TargetUserID, model.SysNotifyGroupJoinRejected, "你接受的群邀请未通过审批", &model.SystemNotificationExtra{
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
GroupAvatar: group.Avatar,
|
||||
Flag: req.Flag,
|
||||
RequestType: string(req.RequestType),
|
||||
RequestStatus: string(model.GroupJoinRequestStatusRejected),
|
||||
Reason: reason,
|
||||
})
|
||||
s.createSystemNotification(req.InitiatorID, model.SysNotifySystem, "你邀请 "+targetUserName+" 入群未通过审批", &model.SystemNotificationExtra{
|
||||
GroupID: group.ID,
|
||||
GroupName: group.Name,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -255,7 +256,7 @@ func (s *PostService) List(ctx context.Context, page, pageSize int, userID strin
|
||||
}
|
||||
cacheKey := cache.PostListKey("latest", visibilityUserKey, page, pageSize)
|
||||
|
||||
result, err := cache.GetOrLoadTyped[*PostListResult](
|
||||
result, err := cache.GetOrLoadTyped(
|
||||
s.cache,
|
||||
cacheKey,
|
||||
postListTTL,
|
||||
@@ -595,8 +596,14 @@ func (s *PostService) GetRecommendedPosts(ctx context.Context, userID string, pa
|
||||
return s.GetHotPosts(ctx, page, pageSize)
|
||||
}
|
||||
|
||||
// 计算偏移量
|
||||
offset := (page - 1) * pageSize
|
||||
// 计算偏移量:第一页使用随机偏移量,增加推荐多样性
|
||||
var offset int
|
||||
if page == 1 {
|
||||
// 第一页随机偏移 0-50,让每次刷新看到不同的推荐内容
|
||||
offset = rand.Intn(51)
|
||||
} else {
|
||||
offset = (page - 1) * pageSize
|
||||
}
|
||||
|
||||
// 从Gorse获取推荐列表
|
||||
// 多取1条用于判断是否还有下一页
|
||||
|
||||
Reference in New Issue
Block a user