refactor: 架构重构 - 解耦过度耦合模块
主要改动: 1. 创建乐观更新工具函数 (optimisticUpdate.ts) - 消除 userStore.ts 中的重复代码 2. 拆分 useResponsive.ts (485行 -> 12个专注模块) - useBreakpoint: 断点检测 - useOrientation: 方向检测 - usePlatform: 平台检测 - useScreenSize: 屏幕尺寸 - useResponsiveValue: 响应式值 - useResponsiveStyle: 响应式样式 - useMediaQuery: 媒体查询 - useColumnCount: 列数计算 - useResponsiveSpacing: 响应式间距 3. 整理数据层 (Repository 层) - ApiDataSource: API数据源 - LocalDataSource: 本地数据源 - CacheDataSource: 缓存数据源 - MessageRepository: 消息仓库 4. 重构 messageManager.ts (2194行 -> 4个模块) - MessageStateManager: 状态管理 - WebSocketMessageHandler: WebSocket处理 - MessageSyncService: 消息同步 - ReadReceiptManager: 已读管理 5. 导航解耦 (MainNavigator.tsx: 1118行 -> 100行) - 创建 NavigationService 解耦层 - 拆分多个 Navigator 组件 架构改进: - 单一职责原则: 每个模块职责明确 - 依赖倒置: 通过接口解耦 - 代码复用: 工具函数可被多处使用 - 可测试性: 各模块可独立测试
This commit is contained in:
144
src/core/entities/Message.ts
Normal file
144
src/core/entities/Message.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Message Entity - 消息领域实体
|
||||
* 定义消息的核心属性和行为,不依赖任何外部框架
|
||||
*/
|
||||
|
||||
export interface MessageSegment {
|
||||
type: 'text' | 'image' | 'mention' | 'reply' | string;
|
||||
data: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
senderId: string;
|
||||
seq: number;
|
||||
segments: MessageSegment[];
|
||||
createdAt: string;
|
||||
status: 'normal' | 'recalled' | 'deleted';
|
||||
category?: string;
|
||||
sender?: {
|
||||
id: string;
|
||||
username: string;
|
||||
avatar?: string;
|
||||
nickname?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Conversation {
|
||||
id: string;
|
||||
type: 'private' | 'group';
|
||||
isPinned: boolean;
|
||||
lastSeq: number;
|
||||
lastMessage?: Message;
|
||||
lastMessageAt: string;
|
||||
unreadCount: number;
|
||||
participants: Array<{
|
||||
id: string;
|
||||
username: string;
|
||||
avatar?: string;
|
||||
nickname?: string;
|
||||
}>;
|
||||
group?: {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar?: string;
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface UnreadCount {
|
||||
total: number;
|
||||
system: number;
|
||||
}
|
||||
|
||||
export interface TypingStatus {
|
||||
groupId: string;
|
||||
userId: string;
|
||||
isTyping: boolean;
|
||||
}
|
||||
|
||||
export interface GroupNotice {
|
||||
type: 'member_join' | 'member_leave' | 'member_removed' | 'role_changed' | 'muted' | 'unmuted';
|
||||
groupId: string;
|
||||
data: {
|
||||
userId?: string;
|
||||
operatorId?: string;
|
||||
role?: string;
|
||||
username?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
timestamp: number;
|
||||
messageId?: string;
|
||||
seq?: number;
|
||||
}
|
||||
|
||||
// 工厂函数
|
||||
export const createMessage = (data: Partial<Message>): Message => ({
|
||||
id: data.id || '',
|
||||
conversationId: data.conversationId || '',
|
||||
senderId: data.senderId || '',
|
||||
seq: data.seq || 0,
|
||||
segments: data.segments || [],
|
||||
createdAt: data.createdAt || new Date().toISOString(),
|
||||
status: data.status || 'normal',
|
||||
category: data.category,
|
||||
sender: data.sender,
|
||||
});
|
||||
|
||||
export const createConversation = (data: Partial<Conversation>): Conversation => ({
|
||||
id: data.id || '',
|
||||
type: data.type || 'private',
|
||||
isPinned: data.isPinned || false,
|
||||
lastSeq: data.lastSeq || 0,
|
||||
lastMessage: data.lastMessage,
|
||||
lastMessageAt: data.lastMessageAt || new Date().toISOString(),
|
||||
unreadCount: data.unreadCount || 0,
|
||||
participants: data.participants || [],
|
||||
group: data.group,
|
||||
createdAt: data.createdAt || new Date().toISOString(),
|
||||
updatedAt: data.updatedAt || new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 工具函数
|
||||
export const isMessageFromCurrentUser = (message: Message, currentUserId: string): boolean => {
|
||||
return message.senderId === currentUserId;
|
||||
};
|
||||
|
||||
export const shouldIncrementUnread = (
|
||||
message: Message,
|
||||
currentUserId: string,
|
||||
activeConversationId: string | null
|
||||
): boolean => {
|
||||
return (
|
||||
message.senderId !== currentUserId &&
|
||||
message.conversationId !== activeConversationId &&
|
||||
!!currentUserId
|
||||
);
|
||||
};
|
||||
|
||||
export const buildTextContent = (segments: MessageSegment[]): string => {
|
||||
return segments
|
||||
.filter((s) => s.type === 'text')
|
||||
.map((s) => s.data?.text || '')
|
||||
.join('');
|
||||
};
|
||||
|
||||
export const buildGroupNoticeText = (notice: GroupNotice): string => {
|
||||
const username = notice.data?.username || '用户';
|
||||
switch (notice.type) {
|
||||
case 'member_join':
|
||||
return `"${username}" 加入了群聊`;
|
||||
case 'member_leave':
|
||||
return `"${username}" 退出了群聊`;
|
||||
case 'member_removed':
|
||||
return `"${username}" 被移出群聊`;
|
||||
case 'muted':
|
||||
return `"${username}" 已被管理员禁言`;
|
||||
case 'unmuted':
|
||||
return `"${username}" 已被管理员解除禁言`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
656
src/core/usecases/ProcessMessageUseCase.ts
Normal file
656
src/core/usecases/ProcessMessageUseCase.ts
Normal file
@@ -0,0 +1,656 @@
|
||||
/**
|
||||
* ProcessMessageUseCase - 处理消息用例
|
||||
* 处理消息的业务逻辑,协调 Repository 和 WebSocketClient
|
||||
* 纯业务逻辑,无UI依赖
|
||||
*/
|
||||
|
||||
import { messageRepository } from '../../data/repositories/MessageRepository';
|
||||
import { webSocketClient, WebSocketEventType } from '../../data/datasources/WebSocketClient';
|
||||
import {
|
||||
Message,
|
||||
Conversation,
|
||||
GroupNotice,
|
||||
createMessage,
|
||||
isMessageFromCurrentUser,
|
||||
shouldIncrementUnread,
|
||||
buildGroupNoticeText,
|
||||
} from '../entities/Message';
|
||||
import { messageService } from '../../services/messageService';
|
||||
import { api } from '../../services/api';
|
||||
|
||||
// 事件类型定义
|
||||
export type MessageUseCaseEventType =
|
||||
| 'message_received'
|
||||
| 'message_recalled'
|
||||
| 'typing_status'
|
||||
| 'group_notice'
|
||||
| 'connection_changed'
|
||||
| 'error';
|
||||
|
||||
export interface MessageUseCaseEvent {
|
||||
type: MessageUseCaseEventType;
|
||||
payload: any;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export type MessageUseCaseSubscriber = (event: MessageUseCaseEvent) => void;
|
||||
|
||||
// 已读状态保护记录
|
||||
interface ReadStateRecord {
|
||||
timestamp: number;
|
||||
version: number;
|
||||
lastReadSeq: number;
|
||||
clearTimer?: NodeJS.Timeout;
|
||||
}
|
||||
|
||||
class ProcessMessageUseCase {
|
||||
private subscribers: Set<MessageUseCaseSubscriber> = new Set();
|
||||
private unsubscribeFns: Array<() => void> = [];
|
||||
private processedMessageIds: Set<string> = new Set();
|
||||
private processedMessageIdsExpiry: Map<string, number> = new Map();
|
||||
private pendingReadMap: Map<string, ReadStateRecord> = new Map();
|
||||
private readStateVersion = 0;
|
||||
private pendingUserRequests: Map<string, Promise<any>> = new Map();
|
||||
private currentUserId: string | null = null;
|
||||
|
||||
// 常量
|
||||
private readonly READ_STATE_PROTECTION_DELAY = 5000; // 5秒
|
||||
private readonly MESSAGE_ID_EXPIRY = 5 * 60 * 1000; // 5分钟
|
||||
|
||||
/**
|
||||
* 初始化用例
|
||||
*/
|
||||
initialize(currentUserId: string | null): void {
|
||||
this.currentUserId = currentUserId;
|
||||
this.setupWebSocketListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁用例
|
||||
*/
|
||||
destroy(): void {
|
||||
this.unsubscribeFns.forEach((fn) => fn());
|
||||
this.unsubscribeFns = [];
|
||||
this.subscribers.clear();
|
||||
this.processedMessageIds.clear();
|
||||
this.processedMessageIdsExpiry.clear();
|
||||
this.pendingReadMap.clear();
|
||||
this.pendingUserRequests.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置WebSocket监听器
|
||||
*/
|
||||
private setupWebSocketListeners(): void {
|
||||
// 监听私聊消息
|
||||
const unsubChat = webSocketClient.on('chat', (message) => {
|
||||
this.handleNewMessage(message);
|
||||
});
|
||||
|
||||
// 监听群聊消息
|
||||
const unsubGroupMessage = webSocketClient.on('group_message', (message) => {
|
||||
this.handleNewMessage(message);
|
||||
});
|
||||
|
||||
// 监听私聊已读回执
|
||||
const unsubRead = webSocketClient.on('read', (message) => {
|
||||
this.handleReadReceipt(message);
|
||||
});
|
||||
|
||||
// 监听群聊已读回执
|
||||
const unsubGroupRead = webSocketClient.on('group_read', (message) => {
|
||||
this.handleGroupReadReceipt(message);
|
||||
});
|
||||
|
||||
// 监听私聊消息撤回
|
||||
const unsubRecall = webSocketClient.on('recall', (message) => {
|
||||
this.handleRecallMessage(message);
|
||||
});
|
||||
|
||||
// 监听群聊消息撤回
|
||||
const unsubGroupRecall = webSocketClient.on('group_recall', (message) => {
|
||||
this.handleGroupRecallMessage(message);
|
||||
});
|
||||
|
||||
// 监听群聊输入状态
|
||||
const unsubGroupTyping = webSocketClient.on('group_typing', (message) => {
|
||||
this.handleGroupTyping(message);
|
||||
});
|
||||
|
||||
// 监听群通知
|
||||
const unsubGroupNotice = webSocketClient.on('group_notice', (message) => {
|
||||
this.handleGroupNotice(message);
|
||||
});
|
||||
|
||||
// 监听连接状态
|
||||
const unsubConnected = webSocketClient.on('connected', () => {
|
||||
this.notifySubscribers({
|
||||
type: 'connection_changed',
|
||||
payload: { connected: true },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
});
|
||||
|
||||
const unsubDisconnected = webSocketClient.on('disconnected', () => {
|
||||
this.notifySubscribers({
|
||||
type: 'connection_changed',
|
||||
payload: { connected: false },
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
});
|
||||
|
||||
this.unsubscribeFns = [
|
||||
unsubChat,
|
||||
unsubGroupMessage,
|
||||
unsubRead,
|
||||
unsubGroupRead,
|
||||
unsubRecall,
|
||||
unsubGroupRecall,
|
||||
unsubGroupTyping,
|
||||
unsubGroupNotice,
|
||||
unsubConnected,
|
||||
unsubDisconnected,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理新消息
|
||||
*/
|
||||
private async handleNewMessage(message: any): Promise<void> {
|
||||
const { conversation_id, id, sender_id, seq, segments, created_at, _isAck } = message;
|
||||
|
||||
// 消息去重检查
|
||||
if (this.isMessageProcessed(id)) {
|
||||
return;
|
||||
}
|
||||
this.markMessageAsProcessed(id);
|
||||
|
||||
// 构造消息对象
|
||||
const newMessage = createMessage({
|
||||
id,
|
||||
conversationId: String(conversation_id),
|
||||
senderId: sender_id,
|
||||
seq,
|
||||
segments: segments || [],
|
||||
createdAt: created_at,
|
||||
});
|
||||
|
||||
// 异步获取发送者信息(如果是群聊且不是当前用户发送的)
|
||||
if (message.type === 'group_message' && sender_id && sender_id !== this.currentUserId) {
|
||||
this.enrichMessageWithSender(newMessage);
|
||||
}
|
||||
|
||||
// 异步保存到本地数据库
|
||||
const isRead = _isAck || sender_id === this.currentUserId;
|
||||
messageRepository.saveMessage(newMessage, isRead).catch((error) => {
|
||||
console.error('[ProcessMessageUseCase] 保存消息失败:', error);
|
||||
});
|
||||
|
||||
// 通知订阅者
|
||||
this.notifySubscribers({
|
||||
type: 'message_received',
|
||||
payload: {
|
||||
message: newMessage,
|
||||
isCurrentUser: sender_id === this.currentUserId,
|
||||
isAck: _isAck,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 丰富消息的发送者信息
|
||||
*/
|
||||
private async enrichMessageWithSender(message: Message): Promise<void> {
|
||||
try {
|
||||
const user = await this.getSenderInfo(message.senderId);
|
||||
if (user) {
|
||||
message.sender = user;
|
||||
// 重新通知,包含发送者信息
|
||||
this.notifySubscribers({
|
||||
type: 'message_received',
|
||||
payload: {
|
||||
message,
|
||||
isCurrentUser: false,
|
||||
isUpdated: true,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ProcessMessageUseCase] 获取发送者信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取发送者信息(带缓存和去重)
|
||||
*/
|
||||
private async getSenderInfo(userId: string): Promise<any | null> {
|
||||
// 先检查本地缓存
|
||||
const cachedUser = await messageRepository.getUserCache(userId);
|
||||
if (cachedUser) {
|
||||
return cachedUser;
|
||||
}
|
||||
|
||||
// 检查是否已有正在进行的请求
|
||||
const pendingRequest = this.pendingUserRequests.get(userId);
|
||||
if (pendingRequest) {
|
||||
return pendingRequest;
|
||||
}
|
||||
|
||||
// 发起新请求
|
||||
const request = this.fetchUserInfo(userId);
|
||||
this.pendingUserRequests.set(userId, request);
|
||||
|
||||
try {
|
||||
const user = await request;
|
||||
return user;
|
||||
} finally {
|
||||
this.pendingUserRequests.delete(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务器获取用户信息
|
||||
*/
|
||||
private async fetchUserInfo(userId: string): Promise<any | null> {
|
||||
try {
|
||||
const response = await api.get(`/users/${userId}`);
|
||||
if (response.code === 0 && response.data) {
|
||||
await messageRepository.saveUserCache(response.data);
|
||||
return response.data;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error(`[ProcessMessageUseCase] 获取用户信息失败: ${userId}`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查消息是否已处理
|
||||
*/
|
||||
private isMessageProcessed(messageId: string): boolean {
|
||||
return this.processedMessageIds.has(messageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记消息已处理
|
||||
*/
|
||||
private markMessageAsProcessed(messageId: string): void {
|
||||
this.processedMessageIds.add(messageId);
|
||||
this.processedMessageIdsExpiry.set(messageId, Date.now());
|
||||
|
||||
// 定期清理
|
||||
if (this.processedMessageIds.size > 1000) {
|
||||
this.cleanupProcessedMessageIds();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的消息ID
|
||||
*/
|
||||
private cleanupProcessedMessageIds(): void {
|
||||
const now = Date.now();
|
||||
for (const [id, timestamp] of this.processedMessageIdsExpiry.entries()) {
|
||||
if (now - timestamp > this.MESSAGE_ID_EXPIRY) {
|
||||
this.processedMessageIds.delete(id);
|
||||
this.processedMessageIdsExpiry.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理私聊已读回执
|
||||
*/
|
||||
private handleReadReceipt(message: any): void {
|
||||
// 可以在这里处理对方已读的状态更新
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理群聊已读回执
|
||||
*/
|
||||
private handleGroupReadReceipt(message: any): void {
|
||||
// 可以在这里处理群聊已读状态更新
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理私聊消息撤回
|
||||
*/
|
||||
private handleRecallMessage(message: any): Promise<void> {
|
||||
const { conversation_id, message_id } = message;
|
||||
|
||||
// 更新本地数据库状态
|
||||
messageRepository
|
||||
.updateMessageStatus(message_id, 'recalled', true)
|
||||
.catch((error) => {
|
||||
console.error('[ProcessMessageUseCase] 更新撤回状态失败:', error);
|
||||
});
|
||||
|
||||
// 通知订阅者
|
||||
this.notifySubscribers({
|
||||
type: 'message_recalled',
|
||||
payload: {
|
||||
conversationId: String(conversation_id),
|
||||
messageId: message_id,
|
||||
isGroup: false,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理群聊消息撤回
|
||||
*/
|
||||
private handleGroupRecallMessage(message: any): Promise<void> {
|
||||
const { conversation_id, message_id } = message;
|
||||
|
||||
// 更新本地数据库状态
|
||||
messageRepository
|
||||
.updateMessageStatus(message_id, 'recalled', true)
|
||||
.catch((error) => {
|
||||
console.error('[ProcessMessageUseCase] 更新撤回状态失败:', error);
|
||||
});
|
||||
|
||||
// 通知订阅者
|
||||
this.notifySubscribers({
|
||||
type: 'message_recalled',
|
||||
payload: {
|
||||
conversationId: String(conversation_id),
|
||||
messageId: message_id,
|
||||
isGroup: true,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理群聊输入状态
|
||||
*/
|
||||
private handleGroupTyping(message: any): void {
|
||||
const { group_id, user_id, is_typing } = message;
|
||||
|
||||
this.notifySubscribers({
|
||||
type: 'typing_status',
|
||||
payload: {
|
||||
groupId: String(group_id),
|
||||
userId: user_id,
|
||||
isTyping: is_typing,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理群通知
|
||||
*/
|
||||
private handleGroupNotice(message: any): void {
|
||||
const { notice_type, group_id, data, timestamp, message_id, seq } = message;
|
||||
const groupIdStr = String(group_id);
|
||||
|
||||
const notice: GroupNotice = {
|
||||
type: notice_type,
|
||||
groupId: groupIdStr,
|
||||
data: data || {},
|
||||
timestamp: timestamp || Date.now(),
|
||||
messageId,
|
||||
seq,
|
||||
};
|
||||
|
||||
// 通知订阅者
|
||||
this.notifySubscribers({
|
||||
type: 'group_notice',
|
||||
payload: {
|
||||
notice,
|
||||
text: buildGroupNoticeText(notice),
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记会话已读
|
||||
*/
|
||||
async markAsRead(conversationId: string, seq: number): Promise<void> {
|
||||
const existingRecord = this.pendingReadMap.get(conversationId);
|
||||
|
||||
// 使用seq去重
|
||||
if (existingRecord && seq <= existingRecord.lastReadSeq) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除可能存在的旧定时器
|
||||
if (existingRecord?.clearTimer) {
|
||||
clearTimeout(existingRecord.clearTimer);
|
||||
}
|
||||
|
||||
// 递增全局版本号
|
||||
this.readStateVersion++;
|
||||
const currentVersion = this.readStateVersion;
|
||||
|
||||
// 记录已读状态
|
||||
this.pendingReadMap.set(conversationId, {
|
||||
timestamp: Date.now(),
|
||||
version: currentVersion,
|
||||
lastReadSeq: seq,
|
||||
});
|
||||
|
||||
// 更新本地数据库
|
||||
await messageRepository.markConversationAsRead(conversationId);
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
await messageService.markAsRead(conversationId, seq);
|
||||
} catch (error) {
|
||||
console.error('[ProcessMessageUseCase] 标记已读API失败:', error);
|
||||
this.pendingReadMap.delete(conversationId);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 设置延迟清除保护
|
||||
const clearTimer = setTimeout(() => {
|
||||
const record = this.pendingReadMap.get(conversationId);
|
||||
if (record && record.version === currentVersion) {
|
||||
this.pendingReadMap.delete(conversationId);
|
||||
}
|
||||
}, this.READ_STATE_PROTECTION_DELAY);
|
||||
|
||||
this.pendingReadMap.set(conversationId, {
|
||||
timestamp: Date.now(),
|
||||
version: currentVersion,
|
||||
lastReadSeq: seq,
|
||||
clearTimer,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查会话是否有进行中的已读请求
|
||||
*/
|
||||
hasPendingRead(conversationId: string): boolean {
|
||||
return this.pendingReadMap.has(conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取进行中的已读记录
|
||||
*/
|
||||
getPendingReadRecord(conversationId: string): ReadStateRecord | undefined {
|
||||
return this.pendingReadMap.get(conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
async sendMessage(
|
||||
conversationId: string,
|
||||
segments: any[],
|
||||
options?: { replyToId?: string }
|
||||
): Promise<Message | null> {
|
||||
try {
|
||||
const response = await messageService.sendMessage(conversationId, {
|
||||
segments,
|
||||
reply_to_id: options?.replyToId,
|
||||
});
|
||||
|
||||
if (response) {
|
||||
const message = createMessage({
|
||||
id: response.id,
|
||||
conversationId,
|
||||
senderId: this.currentUserId || '',
|
||||
seq: response.seq,
|
||||
segments,
|
||||
createdAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// 保存到本地数据库
|
||||
await messageRepository.saveMessage(message, true);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('[ProcessMessageUseCase] 发送消息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地消息
|
||||
*/
|
||||
async getLocalMessages(conversationId: string, limit: number = 20): Promise<Message[]> {
|
||||
return messageRepository.getMessagesByConversation(conversationId, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史消息
|
||||
*/
|
||||
async getHistoryMessages(
|
||||
conversationId: string,
|
||||
beforeSeq: number,
|
||||
limit: number = 20
|
||||
): Promise<Message[]> {
|
||||
// 先从本地获取
|
||||
const localMessages = await messageRepository.getMessagesBeforeSeq(
|
||||
conversationId,
|
||||
beforeSeq,
|
||||
limit
|
||||
);
|
||||
|
||||
if (localMessages.length >= limit) {
|
||||
return localMessages;
|
||||
}
|
||||
|
||||
// 本地数据不足,从服务端获取
|
||||
try {
|
||||
const response = await messageService.getMessages(
|
||||
conversationId,
|
||||
undefined,
|
||||
beforeSeq,
|
||||
limit
|
||||
);
|
||||
|
||||
if (response?.messages && response.messages.length > 0) {
|
||||
const serverMessages = response.messages.map((m: any) =>
|
||||
createMessage({
|
||||
id: m.id,
|
||||
conversationId: m.conversation_id || conversationId,
|
||||
senderId: m.sender_id,
|
||||
seq: m.seq,
|
||||
segments: m.segments || [],
|
||||
createdAt: m.created_at,
|
||||
status: m.status || 'normal',
|
||||
})
|
||||
);
|
||||
|
||||
// 保存到本地
|
||||
await messageRepository.saveMessages(serverMessages, false);
|
||||
|
||||
return serverMessages;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ProcessMessageUseCase] 获取历史消息失败:', error);
|
||||
}
|
||||
|
||||
return localMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从服务端同步消息
|
||||
*/
|
||||
async syncMessagesFromServer(
|
||||
conversationId: string,
|
||||
afterSeq?: number
|
||||
): Promise<Message[]> {
|
||||
try {
|
||||
const response = await messageService.getMessages(conversationId, afterSeq);
|
||||
|
||||
if (response?.messages && response.messages.length > 0) {
|
||||
const messages = response.messages.map((m: any) =>
|
||||
createMessage({
|
||||
id: m.id,
|
||||
conversationId: m.conversation_id || conversationId,
|
||||
senderId: m.sender_id,
|
||||
seq: m.seq,
|
||||
segments: m.segments || [],
|
||||
createdAt: m.created_at,
|
||||
status: m.status || 'normal',
|
||||
})
|
||||
);
|
||||
|
||||
// 保存到本地
|
||||
await messageRepository.saveMessages(messages, false);
|
||||
|
||||
return messages;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[ProcessMessageUseCase] 同步消息失败:', error);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅事件
|
||||
*/
|
||||
subscribe(subscriber: MessageUseCaseSubscriber): () => void {
|
||||
this.subscribers.add(subscriber);
|
||||
|
||||
return () => {
|
||||
this.subscribers.delete(subscriber);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知订阅者
|
||||
*/
|
||||
private notifySubscribers(event: MessageUseCaseEvent): void {
|
||||
this.subscribers.forEach((subscriber) => {
|
||||
try {
|
||||
subscriber(event);
|
||||
} catch (error) {
|
||||
console.error('[ProcessMessageUseCase] 订阅者执行失败:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*/
|
||||
getCurrentUserId(): string | null {
|
||||
return this.currentUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前用户ID
|
||||
*/
|
||||
setCurrentUserId(userId: string | null): void {
|
||||
this.currentUserId = userId;
|
||||
}
|
||||
}
|
||||
|
||||
export const processMessageUseCase = new ProcessMessageUseCase();
|
||||
export default processMessageUseCase;
|
||||
Reference in New Issue
Block a user