Files
frontend/src/stores/message/services/MessageSendService.ts

196 lines
6.6 KiB
TypeScript
Raw Normal View History

/**
*
*
*
*/
import type { MessageResponse, MessageSegment, ConversationResponse } from '../../../types/dto';
2026-04-13 01:30:37 +08:00
import { messageService } from '@/services/message';
import { messageRepository } from '@/database';
import type { IMessageSendService } from '../types';
import { useMessageStore } from '../store';
// 临时ID前缀用于乐观更新
const TEMP_ID_PREFIX = 'temp_';
// 全局自增计数器:与 Date.now() + Math.random() 组合,彻底消除同一毫秒内
// 连续发送导致的临时 ID 碰撞风险addOrReplaceMessage 按 id 幂等,碰撞会吞掉消息)。
let tempIdCounter = 0;
export class MessageSendService implements IMessageSendService {
private getCurrentUserId: () => string | null;
constructor(getCurrentUserId: () => string | null) {
this.getCurrentUserId = getCurrentUserId;
}
/**
* ID
* timestamp + +
*/
private generateTempId(): string {
return `${TEMP_ID_PREFIX}${Date.now()}_${++tempIdCounter}_${Math.random().toString(36).slice(2, 11)}`;
}
/**
*
* 1.
* 2.
* 3.
* 4.
*
* @param detailType 'private' WebSocket HTTP
* 'group' HTTP 'private'
*/
async sendMessage(
conversationId: string,
segments: MessageSegment[],
options?: { replyToId?: string; detailType?: 'private' | 'group' }
): Promise<MessageResponse | null> {
const store = useMessageStore.getState();
const currentUserId = this.getCurrentUserId();
const detailType = options?.detailType ?? 'private';
// 1. 创建临时消息(乐观显示)
const tempId = this.generateTempId();
const tempMessage: MessageResponse = {
id: tempId,
conversation_id: conversationId,
sender_id: currentUserId || '',
seq: 0, // 临时消息seq为0
segments,
reply_to_id: options?.replyToId,
created_at: new Date().toISOString(),
status: 'pending', // 标记为发送中
};
// 2. 立即添加到store用户可以马上看到
store.addOrReplaceMessage(conversationId, tempMessage);
// 3. 更新会话最后消息(乐观更新)
this.updateConversationWithNewMessage(conversationId, tempMessage);
try {
// 4. 调用API发送
const response = await messageService.sendMessage(conversationId, {
segments,
reply_to_id: options?.replyToId,
}, detailType);
if (response) {
// 5. 发送成功:用服务器返回的数据更新临时消息
const successMessage: MessageResponse = {
id: response.id,
conversation_id: conversationId,
sender_id: currentUserId || '',
seq: response.seq,
segments,
reply_to_id: options?.replyToId,
created_at: new Date().toISOString(),
status: 'normal',
};
// 删除临时消息,添加正式消息
store.removeMessage(conversationId, tempId);
store.addOrReplaceMessage(conversationId, successMessage);
// 更新会话最后消息
this.updateConversationWithNewMessage(conversationId, successMessage);
// 保存到本地数据库
const textContent = segments
?.filter((s: any) => s.type === 'text')
.map((s: any) => s.data?.text || '')
.join('') || '';
messageRepository.saveMessage({
id: response.id,
conversationId,
senderId: currentUserId || '',
content: textContent,
type: segments?.find((s: any) => s.type === 'image') ? 'image' : 'text',
isRead: true,
createdAt: successMessage.created_at,
seq: response.seq,
status: 'normal',
segments,
}).catch(console.error);
return successMessage;
}
// 如果服务器没有返回有效响应,标记为失败
const failedMessage: MessageResponse = {
...tempMessage,
status: 'failed',
};
store.addOrReplaceMessage(conversationId, failedMessage);
return null;
} catch (error) {
console.error('[MessageSendService] 发送消息失败:', error);
// 6. 发送失败:更新消息状态为失败
const failedMessage: MessageResponse = {
...tempMessage,
status: 'failed',
};
store.addOrReplaceMessage(conversationId, failedMessage);
return null;
}
}
/**
*
*
*
* 1. ID pending
* 2. segments replyToId sendMessage
* pending / failed
* 3. detailType private
* HTTPsendMessage group
*/
async retrySendMessage(
conversationId: string,
failedMessage: MessageResponse
): Promise<MessageResponse | null> {
if (failedMessage.status !== 'failed') {
console.warn('[MessageSendService] 只能重试发送失败的消息');
return null;
}
const store = useMessageStore.getState();
// 移除旧的失败消息,避免 sendMessage 新插入的 pending 消息与之并存
store.removeMessage(conversationId, String(failedMessage.id));
// 重新发送sendMessage 会重新插入 pending 临时消息并完成后续流程
return this.sendMessage(conversationId, failedMessage.segments, {
replyToId: failedMessage.reply_to_id,
});
}
/**
*
*/
private updateConversationWithNewMessage(
conversationId: string,
message: MessageResponse
): void {
const store = useMessageStore.getState();
const conversation = store.getConversation(conversationId);
const createdAt = message.created_at || new Date().toISOString();
if (conversation) {
// 更新现有会话
const updatedConv: ConversationResponse = {
...conversation,
last_seq: message.seq > 0 ? message.seq : conversation.last_seq,
last_message: message,
last_message_at: createdAt,
updated_at: createdAt,
};
store.updateConversation(updatedConv);
}
}
}