feat(schedule): add course table screens and navigation
Add complete schedule functionality including: - Schedule screen with weekly course table view - Course detail screen with transparent modal presentation - New ScheduleStack navigator integrated into main tab bar - Schedule service for API interactions - Type definitions for course entities Also includes bug fixes for group invite/request handlers to include required groupId parameter.
This commit is contained in:
@@ -4,8 +4,10 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"carrot_bbs/internal/cache"
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/sse"
|
||||
@@ -58,6 +60,9 @@ type chatServiceImpl struct {
|
||||
userRepo *repository.UserRepository
|
||||
sensitive SensitiveService
|
||||
sseHub *sse.Hub
|
||||
|
||||
// 缓存相关字段
|
||||
conversationCache *cache.ConversationCache
|
||||
}
|
||||
|
||||
// NewChatService 创建聊天服务
|
||||
@@ -68,12 +73,25 @@ func NewChatService(
|
||||
sensitive SensitiveService,
|
||||
sseHub *sse.Hub,
|
||||
) ChatService {
|
||||
// 创建适配器
|
||||
convRepoAdapter := cache.NewConversationRepositoryAdapter(repo)
|
||||
msgRepoAdapter := cache.NewMessageRepositoryAdapter(repo)
|
||||
|
||||
// 创建会话缓存
|
||||
conversationCache := cache.NewConversationCache(
|
||||
cache.GetCache(),
|
||||
convRepoAdapter,
|
||||
msgRepoAdapter,
|
||||
cache.DefaultConversationCacheSettings(),
|
||||
)
|
||||
|
||||
return &chatServiceImpl{
|
||||
db: db,
|
||||
repo: repo,
|
||||
userRepo: userRepo,
|
||||
sensitive: sensitive,
|
||||
sseHub: sseHub,
|
||||
db: db,
|
||||
repo: repo,
|
||||
userRepo: userRepo,
|
||||
sensitive: sensitive,
|
||||
sseHub: sseHub,
|
||||
conversationCache: conversationCache,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,18 +104,33 @@ func (s *chatServiceImpl) publishSSEToUsers(userIDs []string, event string, payl
|
||||
|
||||
// GetOrCreateConversation 获取或创建私聊会话
|
||||
func (s *chatServiceImpl) GetOrCreateConversation(ctx context.Context, user1ID, user2ID string) (*model.Conversation, error) {
|
||||
return s.repo.GetOrCreatePrivateConversation(user1ID, user2ID)
|
||||
conv, err := s.repo.GetOrCreatePrivateConversation(user1ID, user2ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 失效会话列表缓存
|
||||
if s.conversationCache != nil {
|
||||
s.conversationCache.InvalidateConversationList(user1ID)
|
||||
s.conversationCache.InvalidateConversationList(user2ID)
|
||||
}
|
||||
|
||||
return conv, nil
|
||||
}
|
||||
|
||||
// GetConversationList 获取用户的会话列表
|
||||
// GetConversationList 获取用户的会话列表(带缓存)
|
||||
func (s *chatServiceImpl) GetConversationList(ctx context.Context, userID string, page, pageSize int) ([]*model.Conversation, int64, error) {
|
||||
// 优先使用缓存
|
||||
if s.conversationCache != nil {
|
||||
return s.conversationCache.GetConversationList(ctx, userID, page, pageSize)
|
||||
}
|
||||
return s.repo.GetConversations(userID, page, pageSize)
|
||||
}
|
||||
|
||||
// GetConversationByID 获取会话详情
|
||||
// GetConversationByID 获取会话详情(带缓存)
|
||||
func (s *chatServiceImpl) GetConversationByID(ctx context.Context, conversationID string, userID string) (*model.Conversation, error) {
|
||||
// 验证用户是否是会话参与者
|
||||
participant, err := s.repo.GetParticipant(conversationID, userID)
|
||||
participant, err := s.getParticipant(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("conversation not found or no permission")
|
||||
@@ -105,21 +138,33 @@ func (s *chatServiceImpl) GetConversationByID(ctx context.Context, conversationI
|
||||
return nil, fmt.Errorf("failed to get participant: %w", err)
|
||||
}
|
||||
|
||||
// 获取会话信息
|
||||
conv, err := s.repo.GetConversation(conversationID)
|
||||
// 获取会话信息(优先使用缓存)
|
||||
var conv *model.Conversation
|
||||
if s.conversationCache != nil {
|
||||
conv, err = s.conversationCache.GetConversation(ctx, conversationID)
|
||||
} else {
|
||||
conv, err = s.repo.GetConversation(conversationID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get conversation: %w", err)
|
||||
}
|
||||
|
||||
// 填充用户的已读位置信息
|
||||
_ = participant // 可以用于返回已读位置等信息
|
||||
|
||||
return conv, nil
|
||||
}
|
||||
|
||||
// getParticipant 获取参与者信息(优先使用缓存)
|
||||
func (s *chatServiceImpl) getParticipant(ctx context.Context, conversationID, userID string) (*model.ConversationParticipant, error) {
|
||||
if s.conversationCache != nil {
|
||||
return s.conversationCache.GetParticipant(ctx, conversationID, userID)
|
||||
}
|
||||
return s.repo.GetParticipant(conversationID, userID)
|
||||
}
|
||||
|
||||
// DeleteConversationForSelf 仅自己删除会话
|
||||
func (s *chatServiceImpl) DeleteConversationForSelf(ctx context.Context, conversationID string, userID string) error {
|
||||
participant, err := s.repo.GetParticipant(conversationID, userID)
|
||||
participant, err := s.getParticipant(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("conversation not found or no permission")
|
||||
@@ -133,12 +178,18 @@ func (s *chatServiceImpl) DeleteConversationForSelf(ctx context.Context, convers
|
||||
if err := s.repo.HideConversationForUser(conversationID, userID); err != nil {
|
||||
return fmt.Errorf("failed to hide conversation: %w", err)
|
||||
}
|
||||
|
||||
// 失效会话列表缓存
|
||||
if s.conversationCache != nil {
|
||||
s.conversationCache.InvalidateConversationList(userID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetConversationPinned 设置会话置顶(用户维度)
|
||||
func (s *chatServiceImpl) SetConversationPinned(ctx context.Context, conversationID string, userID string, isPinned bool) error {
|
||||
participant, err := s.repo.GetParticipant(conversationID, userID)
|
||||
participant, err := s.getParticipant(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("conversation not found or no permission")
|
||||
@@ -152,13 +203,20 @@ func (s *chatServiceImpl) SetConversationPinned(ctx context.Context, conversatio
|
||||
if err := s.repo.UpdatePinned(conversationID, userID, isPinned); err != nil {
|
||||
return fmt.Errorf("failed to update pinned status: %w", err)
|
||||
}
|
||||
|
||||
// 失效缓存
|
||||
if s.conversationCache != nil {
|
||||
s.conversationCache.InvalidateParticipant(conversationID, userID)
|
||||
s.conversationCache.InvalidateConversationList(userID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMessage 发送消息(使用 segments)
|
||||
func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conversationID string, segments model.MessageSegments, replyToID *string) (*model.Message, error) {
|
||||
// 首先验证会话是否存在
|
||||
conv, err := s.repo.GetConversation(conversationID)
|
||||
conv, err := s.getConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("会话不存在,请重新创建会话")
|
||||
@@ -166,9 +224,9 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
return nil, fmt.Errorf("failed to get conversation: %w", err)
|
||||
}
|
||||
|
||||
// 拉黑限制:仅拦截“被拉黑方 -> 拉黑人”方向
|
||||
// 拉黑限制:仅拦截"被拉黑方 -> 拉黑人"方向
|
||||
if conv.Type == model.ConversationTypePrivate && s.userRepo != nil {
|
||||
participants, pErr := s.repo.GetConversationParticipants(conversationID)
|
||||
participants, pErr := s.getParticipants(ctx, conversationID)
|
||||
if pErr != nil {
|
||||
return nil, fmt.Errorf("failed to get participants: %w", pErr)
|
||||
}
|
||||
@@ -209,7 +267,7 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
}
|
||||
|
||||
// 验证用户是否是会话参与者
|
||||
participant, err := s.repo.GetParticipant(conversationID, senderID)
|
||||
participant, err := s.getParticipant(ctx, conversationID, senderID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("您不是该会话的参与者")
|
||||
@@ -231,11 +289,27 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
return nil, fmt.Errorf("failed to save message: %w", err)
|
||||
}
|
||||
|
||||
// 新消息会改变分页结果,先失效分页缓存,避免读到旧列表
|
||||
if s.conversationCache != nil {
|
||||
s.conversationCache.InvalidateMessagePages(conversationID)
|
||||
}
|
||||
|
||||
// 异步写入缓存
|
||||
go func() {
|
||||
if err := s.cacheMessage(context.Background(), conversationID, message); err != nil {
|
||||
log.Printf("[ChatService] async cache message failed, convID=%s, msgID=%s, err=%v", conversationID, message.ID, err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 获取会话中的参与者并发送 SSE
|
||||
participants, err := s.repo.GetConversationParticipants(conversationID)
|
||||
participants, err := s.getParticipants(ctx, conversationID)
|
||||
if err == nil {
|
||||
targetIDs := make([]string, 0, len(participants))
|
||||
for _, p := range participants {
|
||||
// 私聊场景下,发送者已经从 HTTP 响应拿到消息,避免再通过 SSE 回推导致本端重复展示。
|
||||
if conv.Type == model.ConversationTypePrivate && p.UserID == senderID {
|
||||
continue
|
||||
}
|
||||
targetIDs = append(targetIDs, p.UserID)
|
||||
}
|
||||
detailType := "private"
|
||||
@@ -250,6 +324,10 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
if p.UserID == senderID {
|
||||
continue
|
||||
}
|
||||
// 失效未读数缓存
|
||||
if s.conversationCache != nil {
|
||||
s.conversationCache.InvalidateUnreadCount(p.UserID, conversationID)
|
||||
}
|
||||
if totalUnread, uErr := s.repo.GetAllUnreadCount(p.UserID); uErr == nil {
|
||||
s.publishSSEToUsers([]string{p.UserID}, "conversation_unread", map[string]interface{}{
|
||||
"conversation_id": conversationID,
|
||||
@@ -259,11 +337,46 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
}
|
||||
}
|
||||
|
||||
// 失效会话列表缓存
|
||||
if s.conversationCache != nil {
|
||||
for _, p := range participants {
|
||||
s.conversationCache.InvalidateConversationList(p.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
_ = participant // 避免未使用变量警告
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// getConversation 获取会话(优先使用缓存)
|
||||
func (s *chatServiceImpl) getConversation(ctx context.Context, conversationID string) (*model.Conversation, error) {
|
||||
if s.conversationCache != nil {
|
||||
return s.conversationCache.GetConversation(ctx, conversationID)
|
||||
}
|
||||
return s.repo.GetConversation(conversationID)
|
||||
}
|
||||
|
||||
// getParticipants 获取会话参与者(优先使用缓存)
|
||||
func (s *chatServiceImpl) getParticipants(ctx context.Context, conversationID string) ([]*model.ConversationParticipant, error) {
|
||||
if s.conversationCache != nil {
|
||||
return s.conversationCache.GetParticipants(ctx, conversationID)
|
||||
}
|
||||
return s.repo.GetConversationParticipants(conversationID)
|
||||
}
|
||||
|
||||
// cacheMessage 缓存消息(内部方法)
|
||||
func (s *chatServiceImpl) cacheMessage(ctx context.Context, convID string, msg *model.Message) error {
|
||||
if s.conversationCache == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
asyncCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return s.conversationCache.CacheMessage(asyncCtx, convID, msg)
|
||||
}
|
||||
|
||||
func containsImageSegment(segments model.MessageSegments) bool {
|
||||
for _, seg := range segments {
|
||||
if seg.Type == string(model.ContentTypeImage) || seg.Type == "image" {
|
||||
@@ -273,10 +386,10 @@ func containsImageSegment(segments model.MessageSegments) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetMessages 获取消息历史(分页)
|
||||
// GetMessages 获取消息历史(分页,带缓存)
|
||||
func (s *chatServiceImpl) GetMessages(ctx context.Context, conversationID string, userID string, page, pageSize int) ([]*model.Message, int64, error) {
|
||||
// 验证用户是否是会话参与者
|
||||
_, err := s.repo.GetParticipant(conversationID, userID)
|
||||
_, err := s.getParticipant(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, 0, errors.New("conversation not found or no permission")
|
||||
@@ -284,13 +397,18 @@ func (s *chatServiceImpl) GetMessages(ctx context.Context, conversationID string
|
||||
return nil, 0, fmt.Errorf("failed to get participant: %w", err)
|
||||
}
|
||||
|
||||
// 优先使用缓存
|
||||
if s.conversationCache != nil {
|
||||
return s.conversationCache.GetMessages(ctx, conversationID, page, pageSize)
|
||||
}
|
||||
|
||||
return s.repo.GetMessages(conversationID, page, pageSize)
|
||||
}
|
||||
|
||||
// GetMessagesAfterSeq 获取指定seq之后的消息(用于增量同步)
|
||||
func (s *chatServiceImpl) GetMessagesAfterSeq(ctx context.Context, conversationID string, userID string, afterSeq int64, limit int) ([]*model.Message, error) {
|
||||
// 验证用户是否是会话参与者
|
||||
_, err := s.repo.GetParticipant(conversationID, userID)
|
||||
_, err := s.getParticipant(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("conversation not found or no permission")
|
||||
@@ -308,7 +426,7 @@ func (s *chatServiceImpl) GetMessagesAfterSeq(ctx context.Context, conversationI
|
||||
// GetMessagesBeforeSeq 获取指定seq之前的历史消息(用于下拉加载更多)
|
||||
func (s *chatServiceImpl) GetMessagesBeforeSeq(ctx context.Context, conversationID string, userID string, beforeSeq int64, limit int) ([]*model.Message, error) {
|
||||
// 验证用户是否是会话参与者
|
||||
_, err := s.repo.GetParticipant(conversationID, userID)
|
||||
_, err := s.getParticipant(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("conversation not found or no permission")
|
||||
@@ -326,7 +444,7 @@ func (s *chatServiceImpl) GetMessagesBeforeSeq(ctx context.Context, conversation
|
||||
// MarkAsRead 标记已读
|
||||
func (s *chatServiceImpl) MarkAsRead(ctx context.Context, conversationID string, userID string, seq int64) error {
|
||||
// 验证用户是否是会话参与者
|
||||
_, err := s.repo.GetParticipant(conversationID, userID)
|
||||
_, err := s.getParticipant(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("conversation not found or no permission")
|
||||
@@ -334,17 +452,27 @@ func (s *chatServiceImpl) MarkAsRead(ctx context.Context, conversationID string,
|
||||
return fmt.Errorf("failed to get participant: %w", err)
|
||||
}
|
||||
|
||||
// 更新参与者的已读位置
|
||||
// 1. 先写入DB(保证数据一致性,DB是唯一数据源)
|
||||
err = s.repo.UpdateLastReadSeq(conversationID, userID, seq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update last read seq: %w", err)
|
||||
}
|
||||
|
||||
participants, pErr := s.repo.GetConversationParticipants(conversationID)
|
||||
// 2. DB 写入成功后,失效缓存(Cache-Aside 模式)
|
||||
if s.conversationCache != nil {
|
||||
// 失效参与者缓存,下次读取时会从 DB 加载最新数据
|
||||
s.conversationCache.InvalidateParticipant(conversationID, userID)
|
||||
// 失效未读数缓存
|
||||
s.conversationCache.InvalidateUnreadCount(userID, conversationID)
|
||||
// 失效会话列表缓存
|
||||
s.conversationCache.InvalidateConversationList(userID)
|
||||
}
|
||||
|
||||
participants, pErr := s.getParticipants(ctx, conversationID)
|
||||
if pErr == nil {
|
||||
detailType := "private"
|
||||
groupID := ""
|
||||
if conv, convErr := s.repo.GetConversation(conversationID); convErr == nil && conv.Type == model.ConversationTypeGroup {
|
||||
if conv, convErr := s.getConversation(ctx, conversationID); convErr == nil && conv.Type == model.ConversationTypeGroup {
|
||||
detailType = "group"
|
||||
if conv.GroupID != nil {
|
||||
groupID = *conv.GroupID
|
||||
@@ -372,10 +500,10 @@ func (s *chatServiceImpl) MarkAsRead(ctx context.Context, conversationID string,
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUnreadCount 获取指定会话的未读消息数
|
||||
// GetUnreadCount 获取指定会话的未读消息数(带缓存)
|
||||
func (s *chatServiceImpl) GetUnreadCount(ctx context.Context, conversationID string, userID string) (int64, error) {
|
||||
// 验证用户是否是会话参与者
|
||||
_, err := s.repo.GetParticipant(conversationID, userID)
|
||||
_, err := s.getParticipant(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, errors.New("conversation not found or no permission")
|
||||
@@ -383,6 +511,11 @@ func (s *chatServiceImpl) GetUnreadCount(ctx context.Context, conversationID str
|
||||
return 0, fmt.Errorf("failed to get participant: %w", err)
|
||||
}
|
||||
|
||||
// 优先使用缓存
|
||||
if s.conversationCache != nil {
|
||||
return s.conversationCache.GetUnreadCount(ctx, userID, conversationID)
|
||||
}
|
||||
|
||||
return s.repo.GetUnreadCount(conversationID, userID)
|
||||
}
|
||||
|
||||
@@ -427,10 +560,15 @@ func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, u
|
||||
return fmt.Errorf("failed to recall message: %w", err)
|
||||
}
|
||||
|
||||
if participants, pErr := s.repo.GetConversationParticipants(message.ConversationID); pErr == nil {
|
||||
// 失效消息缓存
|
||||
if s.conversationCache != nil {
|
||||
s.conversationCache.InvalidateConversation(message.ConversationID)
|
||||
}
|
||||
|
||||
if participants, pErr := s.getParticipants(ctx, message.ConversationID); pErr == nil {
|
||||
detailType := "private"
|
||||
groupID := ""
|
||||
if conv, convErr := s.repo.GetConversation(message.ConversationID); convErr == nil && conv.Type == model.ConversationTypeGroup {
|
||||
if conv, convErr := s.getConversation(ctx, message.ConversationID); convErr == nil && conv.Type == model.ConversationTypeGroup {
|
||||
detailType = "group"
|
||||
if conv.GroupID != nil {
|
||||
groupID = *conv.GroupID
|
||||
@@ -465,7 +603,7 @@ func (s *chatServiceImpl) DeleteMessage(ctx context.Context, messageID string, u
|
||||
}
|
||||
|
||||
// 验证用户是否是会话参与者
|
||||
_, err = s.repo.GetParticipant(message.ConversationID, userID)
|
||||
_, err = s.getParticipant(ctx, message.ConversationID, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.New("no permission to delete this message")
|
||||
@@ -485,6 +623,11 @@ func (s *chatServiceImpl) DeleteMessage(ctx context.Context, messageID string, u
|
||||
return fmt.Errorf("failed to delete message: %w", err)
|
||||
}
|
||||
|
||||
// 失效消息缓存
|
||||
if s.conversationCache != nil {
|
||||
s.conversationCache.InvalidateConversation(message.ConversationID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -495,19 +638,19 @@ func (s *chatServiceImpl) SendTyping(ctx context.Context, senderID string, conve
|
||||
}
|
||||
|
||||
// 验证用户是否是会话参与者
|
||||
_, err := s.repo.GetParticipant(conversationID, senderID)
|
||||
_, err := s.getParticipant(ctx, conversationID, senderID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取会话中的其他参与者
|
||||
participants, err := s.repo.GetConversationParticipants(conversationID)
|
||||
participants, err := s.getParticipants(ctx, conversationID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
detailType := "private"
|
||||
if conv, convErr := s.repo.GetConversation(conversationID); convErr == nil && conv.Type == model.ConversationTypeGroup {
|
||||
if conv, convErr := s.getConversation(ctx, conversationID); convErr == nil && conv.Type == model.ConversationTypeGroup {
|
||||
detailType = "group"
|
||||
}
|
||||
for _, p := range participants {
|
||||
@@ -537,7 +680,7 @@ func (s *chatServiceImpl) IsUserOnline(userID string) bool {
|
||||
// 适用于群聊等由调用方自行负责推送的场景
|
||||
func (s *chatServiceImpl) SaveMessage(ctx context.Context, senderID string, conversationID string, segments model.MessageSegments, replyToID *string) (*model.Message, error) {
|
||||
// 验证会话是否存在
|
||||
_, err := s.repo.GetConversation(conversationID)
|
||||
_, err := s.getConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("会话不存在,请重新创建会话")
|
||||
@@ -546,7 +689,7 @@ func (s *chatServiceImpl) SaveMessage(ctx context.Context, senderID string, conv
|
||||
}
|
||||
|
||||
// 验证用户是否是会话参与者
|
||||
_, err = s.repo.GetParticipant(conversationID, senderID)
|
||||
_, err = s.getParticipant(ctx, conversationID, senderID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("您不是该会话的参与者")
|
||||
@@ -566,5 +709,17 @@ func (s *chatServiceImpl) SaveMessage(ctx context.Context, senderID string, conv
|
||||
return nil, fmt.Errorf("failed to save message: %w", err)
|
||||
}
|
||||
|
||||
// 新消息会改变分页结果,先失效分页缓存,避免读到旧列表
|
||||
if s.conversationCache != nil {
|
||||
s.conversationCache.InvalidateMessagePages(conversationID)
|
||||
}
|
||||
|
||||
// 异步写入缓存
|
||||
go func() {
|
||||
if err := s.cacheMessage(context.Background(), conversationID, message); err != nil {
|
||||
log.Printf("[ChatService] async cache message failed, convID=%s, msgID=%s, err=%v", conversationID, message.ID, err)
|
||||
}
|
||||
}()
|
||||
|
||||
return message, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user