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:
lafay
2026-03-18 12:11:49 +08:00
parent 1ffbb63753
commit a6cdb97e24
70 changed files with 8175 additions and 1728 deletions

View File

@@ -0,0 +1,103 @@
/**
* API 数据源实现
* 封装所有 API 调用,统一错误处理和请求拦截
*/
import { api, ApiResponse, ApiError } from '../../services/api';
import { IApiDataSource, DataSourceError } from './interfaces';
export class ApiDataSource implements IApiDataSource {
private baseUrl: string;
constructor(baseUrl?: string) {
this.baseUrl = baseUrl || '';
}
private buildUrl(url: string): string {
if (url.startsWith('http')) {
return url;
}
return `${this.baseUrl}${url}`;
}
private handleError(error: unknown, operation: string): never {
if (error instanceof ApiError) {
throw new DataSourceError(
error.message,
String(error.code),
'ApiDataSource',
error
);
}
const message = error instanceof Error ? error.message : 'Unknown error';
throw new DataSourceError(
`API ${operation} failed: ${message}`,
'API_ERROR',
'ApiDataSource',
error instanceof Error ? error : undefined
);
}
async get<T>(url: string, params?: any): Promise<T> {
try {
const fullUrl = this.buildUrl(url);
const response = await api.get<T>(fullUrl, params);
return response.data;
} catch (error) {
this.handleError(error, 'GET');
}
}
async post<T>(url: string, data?: any): Promise<T> {
try {
const fullUrl = this.buildUrl(url);
const response = await api.post<T>(fullUrl, data);
return response.data;
} catch (error) {
this.handleError(error, 'POST');
}
}
async put<T>(url: string, data?: any): Promise<T> {
try {
const fullUrl = this.buildUrl(url);
const response = await api.put<T>(fullUrl, data);
return response.data;
} catch (error) {
this.handleError(error, 'PUT');
}
}
async delete<T>(url: string, data?: any): Promise<T> {
try {
const fullUrl = this.buildUrl(url);
// 处理带 request body 的 DELETE 请求
if (data) {
const response = await api.request<T>('DELETE', fullUrl, undefined, data);
return response.data;
}
const response = await api.delete<T>(fullUrl);
return response.data;
} catch (error) {
this.handleError(error, 'DELETE');
}
}
async upload<T>(
url: string,
file: { uri: string; name: string; type: string },
additionalData?: Record<string, string>
): Promise<T> {
try {
const fullUrl = this.buildUrl(url);
const response = await api.upload<T>(fullUrl, file, additionalData);
return response.data;
} catch (error) {
this.handleError(error, 'UPLOAD');
}
}
}
// 导出单例实例
export const apiDataSource = new ApiDataSource();

View File

@@ -0,0 +1,199 @@
/**
* 缓存数据源实现
* 基于内存和 AsyncStorage 的混合缓存实现
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import { ICacheDataSource, DataSourceError } from './interfaces';
interface CacheEntry<T> {
value: T;
expiry: number | null; // null 表示永不过期
}
export class CacheDataSource implements ICacheDataSource {
private memoryCache: Map<string, CacheEntry<any>> = new Map();
private maxSize: number;
private defaultTtl: number; // 默认过期时间(毫秒)
private persistPrefix: string;
constructor(config?: {
maxSize?: number;
defaultTtl?: number; // 默认5分钟
persistPrefix?: string;
}) {
this.maxSize = config?.maxSize || 100;
this.defaultTtl = config?.defaultTtl || 5 * 60 * 1000;
this.persistPrefix = config?.persistPrefix || 'cache_';
}
/**
* 检查 key 是否过期
*/
private isExpired(entry: CacheEntry<any>): boolean {
if (entry.expiry === null) return false;
return Date.now() > entry.expiry;
}
/**
* 清理过期缓存
*/
private cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.memoryCache.entries()) {
if (entry.expiry !== null && now > entry.expiry) {
this.memoryCache.delete(key);
}
}
// 如果仍然超过最大大小,删除最旧的条目
while (this.memoryCache.size > this.maxSize) {
const firstKey = this.memoryCache.keys().next().value;
if (firstKey !== undefined) {
this.memoryCache.delete(firstKey);
}
}
}
/**
* 生成持久化存储 key
*/
private getPersistKey(key: string): string {
return `${this.persistPrefix}${key}`;
}
// ==================== ICacheDataSource 实现 ====================
async get<T>(key: string): Promise<T | null> {
try {
// 先检查内存缓存
const memoryEntry = this.memoryCache.get(key);
if (memoryEntry) {
if (!this.isExpired(memoryEntry)) {
return memoryEntry.value as T;
}
// 过期了,从内存中删除
this.memoryCache.delete(key);
}
// 尝试从持久化存储读取
const persistKey = this.getPersistKey(key);
const stored = await AsyncStorage.getItem(persistKey);
if (stored) {
const entry: CacheEntry<T> = JSON.parse(stored);
if (!this.isExpired(entry)) {
// 重新加载到内存缓存
this.memoryCache.set(key, entry);
this.cleanup();
return entry.value;
}
// 过期了,删除
await AsyncStorage.removeItem(persistKey);
}
return null;
} catch (error) {
console.warn(`[CacheDataSource] Failed to get ${key}:`, error);
return null;
}
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
try {
const expiry = ttl === undefined
? (this.defaultTtl > 0 ? Date.now() + this.defaultTtl : null)
: (ttl > 0 ? Date.now() + ttl : null);
const entry: CacheEntry<T> = { value, expiry };
// 存入内存
this.memoryCache.set(key, entry);
this.cleanup();
// 持久化存储(重要数据)
if (ttl === undefined || ttl > 0) {
const persistKey = this.getPersistKey(key);
await AsyncStorage.setItem(persistKey, JSON.stringify(entry));
}
} catch (error) {
throw new DataSourceError(
`Failed to set cache ${key}`,
'CACHE_SET_ERROR',
'CacheDataSource',
error instanceof Error ? error : undefined
);
}
}
async delete(key: string): Promise<void> {
try {
// 从内存删除
this.memoryCache.delete(key);
// 从持久化存储删除
const persistKey = this.getPersistKey(key);
await AsyncStorage.removeItem(persistKey);
} catch (error) {
throw new DataSourceError(
`Failed to delete cache ${key}`,
'CACHE_DELETE_ERROR',
'CacheDataSource',
error instanceof Error ? error : undefined
);
}
}
async clear(): Promise<void> {
try {
// 清空内存
this.memoryCache.clear();
// 清空持久化存储中的所有缓存项
const keys = await AsyncStorage.getAllKeys();
const cacheKeys = keys.filter(k => k.startsWith(this.persistPrefix));
if (cacheKeys.length > 0) {
await AsyncStorage.multiRemove(cacheKeys);
}
} catch (error) {
throw new DataSourceError(
'Failed to clear cache',
'CACHE_CLEAR_ERROR',
'CacheDataSource',
error instanceof Error ? error : undefined
);
}
}
async has(key: string): Promise<boolean> {
try {
// 检查内存
const memoryEntry = this.memoryCache.get(key);
if (memoryEntry && !this.isExpired(memoryEntry)) {
return true;
}
// 检查持久化存储
const persistKey = this.getPersistKey(key);
const stored = await AsyncStorage.getItem(persistKey);
if (stored) {
const entry: CacheEntry<any> = JSON.parse(stored);
return !this.isExpired(entry);
}
return false;
} catch (error) {
return false;
}
}
async getMultiple<T>(keys: string[]): Promise<(T | null)[]> {
return Promise.all(keys.map(key => this.get<T>(key)));
}
async setMultiple<T>(entries: { key: string; value: T; ttl?: number }[]): Promise<void> {
await Promise.all(entries.map(entry => this.set(entry.key, entry.value, entry.ttl)));
}
}
// 导出单例实例
export const cacheDataSource = new CacheDataSource();

View File

@@ -0,0 +1,281 @@
/**
* 本地数据库数据源实现
* 封装所有 SQLite 操作,提供统一的错误处理和事务支持
*/
import * as SQLite from 'expo-sqlite';
import { ILocalDataSource, DataSourceError } from './interfaces';
// 数据库实例管理
let dbInstance: SQLite.SQLiteDatabase | null = null;
let currentDbName: string | null = null;
let writeQueue: Promise<void> = Promise.resolve();
export interface LocalDataSourceConfig {
dbName?: string;
userId?: string;
}
export class LocalDataSource implements ILocalDataSource {
private db: SQLite.SQLiteDatabase | null = null;
private dbName: string;
private initialized = false;
constructor(config: LocalDataSourceConfig = {}) {
// 如果提供了userId使用用户专属数据库
this.dbName = config.dbName || (config.userId ? `carrot_bbs_${config.userId}.db` : 'carrot_bbs.db');
}
/**
* 初始化数据库连接
*/
async initialize(): Promise<void> {
if (this.initialized && this.db) {
return;
}
try {
// 使用全局实例管理
if (dbInstance && currentDbName === this.dbName) {
this.db = dbInstance;
this.initialized = true;
return;
}
// 关闭旧连接
if (dbInstance) {
try {
await dbInstance.closeAsync();
} catch (e) {
console.warn('关闭旧数据库连接失败:', e);
}
}
// 创建新连接
this.db = await SQLite.openDatabaseAsync(this.dbName);
dbInstance = this.db;
currentDbName = this.dbName;
this.initialized = true;
// 初始化数据库表结构
await this.createTables();
} catch (error) {
this.handleError(error, 'INITIALIZE');
}
}
/**
* 创建数据库表结构
*/
private async createTables(): Promise<void> {
if (!this.db) return;
const tables = [
// 消息表
`CREATE TABLE IF NOT EXISTS messages (
id TEXT PRIMARY KEY NOT NULL,
conversationId TEXT NOT NULL,
senderId TEXT NOT NULL,
content TEXT,
type TEXT DEFAULT 'text',
isRead INTEGER DEFAULT 0,
createdAt TEXT NOT NULL,
seq INTEGER DEFAULT 0,
status TEXT DEFAULT 'normal',
segments TEXT
)`,
// 会话表
`CREATE TABLE IF NOT EXISTS conversations (
id TEXT PRIMARY KEY NOT NULL,
participantId TEXT NOT NULL,
lastMessageId TEXT,
lastSeq INTEGER DEFAULT 0,
unreadCount INTEGER DEFAULT 0,
createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL
)`,
// 会话缓存表
`CREATE TABLE IF NOT EXISTS conversation_cache (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
)`,
// 会话列表缓存表
`CREATE TABLE IF NOT EXISTS conversation_list_cache (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
)`,
// 用户缓存表
`CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
)`,
// 当前登录用户缓存
`CREATE TABLE IF NOT EXISTS current_user_cache (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
)`,
// 群组缓存表
`CREATE TABLE IF NOT EXISTS groups (
id TEXT PRIMARY KEY NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL
)`,
// 群成员缓存表
`CREATE TABLE IF NOT EXISTS group_members (
groupId TEXT NOT NULL,
userId TEXT NOT NULL,
data TEXT NOT NULL,
updatedAt TEXT NOT NULL,
PRIMARY KEY (groupId, userId)
)`,
];
for (const sql of tables) {
await this.db.execAsync(sql);
}
// 创建索引
const indexes = [
`CREATE INDEX IF NOT EXISTS idx_messages_conversationId ON messages(conversationId)`,
`CREATE INDEX IF NOT EXISTS idx_messages_seq ON messages(seq)`,
`CREATE INDEX IF NOT EXISTS idx_messages_conversation_seq ON messages(conversationId, seq)`,
`CREATE INDEX IF NOT EXISTS idx_group_members_groupId ON group_members(groupId)`,
];
for (const sql of indexes) {
await this.db.execAsync(sql);
}
}
/**
* 处理错误
*/
private handleError(error: unknown, operation: string): never {
const message = error instanceof Error ? error.message : 'Unknown error';
throw new DataSourceError(
`Database ${operation} failed: ${message}`,
'DB_ERROR',
'LocalDataSource',
error instanceof Error ? error : undefined
);
}
/**
* 检查数据库连接
*/
private ensureDb(): SQLite.SQLiteDatabase {
if (!this.db) {
throw new DataSourceError(
'Database not initialized',
'DB_NOT_INITIALIZED',
'LocalDataSource'
);
}
return this.db;
}
// ==================== ILocalDataSource 实现 ====================
async query<T>(sql: string, params?: any[]): Promise<T[]> {
try {
const db = this.ensureDb();
return await db.getAllAsync<T>(sql, params);
} catch (error) {
this.handleError(error, 'QUERY');
}
}
async getFirst<T>(sql: string, params?: any[]): Promise<T | null> {
try {
const db = this.ensureDb();
return await db.getFirstAsync<T>(sql, params);
} catch (error) {
this.handleError(error, 'GET_FIRST');
}
}
async execute(sql: string, params?: any[]): Promise<void> {
try {
const db = this.ensureDb();
await db.execAsync(sql);
} catch (error) {
this.handleError(error, 'EXECUTE');
}
}
async run(sql: string, params?: any[]): Promise<{ lastInsertRowId: number; changes: number }> {
try {
const db = this.ensureDb();
return await db.runAsync(sql, params);
} catch (error) {
this.handleError(error, 'RUN');
}
}
async transaction(operations: () => Promise<void>): Promise<void> {
try {
const db = this.ensureDb();
await db.withTransactionAsync(async () => {
await operations();
});
} catch (error) {
this.handleError(error, 'TRANSACTION');
}
}
async batch(operations: (db: ILocalDataSource) => Promise<void>): Promise<void> {
try {
await this.transaction(async () => {
await operations(this);
});
} catch (error) {
this.handleError(error, 'BATCH');
}
}
// ==================== 队列写入支持 ====================
/**
* 使用队列执行写入操作(避免并发写入冲突)
*/
async enqueueWrite<T>(operation: () => Promise<T>): Promise<T> {
const wrappedOperation = async () => {
try {
return await operation();
} catch (error) {
// 尝试重连后重试
if (this.isRecoverableError(error)) {
console.error('数据库写入异常,尝试重连后重试:', error);
this.db = null;
dbInstance = null;
await this.initialize();
return operation();
}
throw error;
}
};
const queued = writeQueue.then(wrappedOperation, wrappedOperation);
writeQueue = queued.then(() => undefined, () => undefined);
return queued;
}
/**
* 判断错误是否可恢复
*/
private isRecoverableError(error: unknown): boolean {
const message = String(error);
return (
message.includes('NativeDatabase.prepareAsync') ||
message.includes('NullPointerException') ||
message.includes('database is locked')
);
}
}
// 导出单例实例
export const localDataSource = new LocalDataSource();

View File

@@ -0,0 +1,184 @@
/**
* WebSocketClient - WebSocket连接管理
* 只负责WebSocket连接管理提供事件驱动架构
*/
import {
sseService,
WSChatMessage,
WSGroupChatMessage,
WSReadMessage,
WSGroupReadMessage,
WSRecallMessage,
WSGroupRecallMessage,
WSGroupTypingMessage,
WSGroupNoticeMessage,
WSMessageType,
} from '../../services/sseService';
// 事件处理器类型
type EventHandler<T> = (data: T) => void;
// WebSocket事件类型
export interface WebSocketEvents {
'chat': WSChatMessage;
'group_message': WSGroupChatMessage;
'read': WSReadMessage;
'group_read': WSGroupReadMessage;
'recall': WSRecallMessage;
'group_recall': WSGroupRecallMessage;
'group_typing': WSGroupTypingMessage;
'group_notice': WSGroupNoticeMessage;
'connected': void;
'disconnected': void;
'error': Error;
}
export type WebSocketEventType = keyof WebSocketEvents;
class WebSocketClient {
private handlers: Map<WebSocketEventType, Set<EventHandler<any>>> = new Map();
private unsubscribeFns: Array<() => void> = [];
private isInitialized = false;
/**
* 初始化WebSocket监听
*/
initialize(): void {
if (this.isInitialized) return;
// 监听私聊消息
const unsubChat = sseService.on('chat', (message) => {
this.emit('chat', message);
});
// 监听群聊消息
const unsubGroupMessage = sseService.on('group_message', (message) => {
this.emit('group_message', message);
});
// 监听私聊已读回执
const unsubRead = sseService.on('read', (message) => {
this.emit('read', message);
});
// 监听群聊已读回执
const unsubGroupRead = sseService.on('group_read', (message) => {
this.emit('group_read', message);
});
// 监听私聊消息撤回
const unsubRecall = sseService.on('recall', (message) => {
this.emit('recall', message);
});
// 监听群聊消息撤回
const unsubGroupRecall = sseService.on('group_recall', (message) => {
this.emit('group_recall', message);
});
// 监听群聊输入状态
const unsubGroupTyping = sseService.on('group_typing', (message) => {
this.emit('group_typing', message);
});
// 监听群通知
const unsubGroupNotice = sseService.on('group_notice', (message) => {
this.emit('group_notice', message);
});
// 监听连接状态
const unsubConnect = sseService.onConnect(() => {
this.emit('connected', undefined);
});
const unsubDisconnect = sseService.onDisconnect(() => {
this.emit('disconnected', undefined);
});
this.unsubscribeFns = [
unsubChat,
unsubGroupMessage,
unsubRead,
unsubGroupRead,
unsubRecall,
unsubGroupRecall,
unsubGroupTyping,
unsubGroupNotice,
unsubConnect,
unsubDisconnect,
];
this.isInitialized = true;
}
/**
* 销毁WebSocket监听
*/
destroy(): void {
this.unsubscribeFns.forEach((fn) => fn());
this.unsubscribeFns = [];
this.handlers.clear();
this.isInitialized = false;
}
/**
* 订阅事件
*/
on<T extends WebSocketEventType>(
event: T,
handler: EventHandler<WebSocketEvents[T]>
): () => void {
if (!this.handlers.has(event)) {
this.handlers.set(event, new Set());
}
this.handlers.get(event)!.add(handler);
return () => {
this.handlers.get(event)?.delete(handler);
};
}
/**
* 触发事件
*/
private emit<T extends WebSocketEventType>(
event: T,
data: WebSocketEvents[T]
): void {
const handlers = this.handlers.get(event);
if (handlers) {
handlers.forEach((handler) => {
try {
handler(data);
} catch (error) {
console.error(`[WebSocketClient] 事件处理器执行失败: ${event}`, error);
}
});
}
}
/**
* 检查连接状态
*/
isConnected(): boolean {
return sseService.isConnected();
}
/**
* 启动WebSocket连接
*/
async connect(): Promise<boolean> {
return sseService.start();
}
/**
* 断开WebSocket连接
*/
disconnect(): void {
sseService.stop();
}
}
export const webSocketClient = new WebSocketClient();
export default webSocketClient;

View File

@@ -0,0 +1,9 @@
/**
* 数据源导出
* 统一导出所有数据源实现
*/
export * from './interfaces';
export * from './ApiDataSource';
export * from './LocalDataSource';
export * from './CacheDataSource';

View File

@@ -0,0 +1,64 @@
/**
* 数据源接口定义
* 定义所有数据源的标准接口,便于替换和测试
*/
// API 数据源接口
export interface IApiDataSource {
get<T>(url: string, params?: any): Promise<T>;
post<T>(url: string, data?: any): Promise<T>;
put<T>(url: string, data?: any): Promise<T>;
delete<T>(url: string, data?: any): Promise<T>;
upload<T>(url: string, file: { uri: string; name: string; type: string }, additionalData?: Record<string, string>): Promise<T>;
}
// 本地数据源接口 (SQLite)
export interface ILocalDataSource {
query<T>(sql: string, params?: any[]): Promise<T[]>;
getFirst<T>(sql: string, params?: any[]): Promise<T | null>;
execute(sql: string, params?: any[]): Promise<void>;
run(sql: string, params?: any[]): Promise<{ lastInsertRowId: number; changes: number }>;
transaction(operations: () => Promise<void>): Promise<void>;
batch(operations: (db: ILocalDataSource) => Promise<void>): Promise<void>;
}
// 缓存数据源接口
export interface ICacheDataSource {
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T, ttl?: number): Promise<void>;
delete(key: string): Promise<void>;
clear(): Promise<void>;
has(key: string): Promise<boolean>;
getMultiple<T>(keys: string[]): Promise<(T | null)[]>;
setMultiple<T>(entries: { key: string; value: T; ttl?: number }[]): Promise<void>;
}
// 数据源错误类型
export class DataSourceError extends Error {
constructor(
message: string,
public code: string,
public source: string,
public originalError?: Error
) {
super(message);
this.name = 'DataSourceError';
}
}
// 数据源配置
export interface DataSourceConfig {
api?: {
baseUrl: string;
timeout?: number;
retryCount?: number;
};
local?: {
dbName: string;
version: number;
};
cache?: {
maxSize: number;
defaultTtl: number;
};
}

View File

@@ -0,0 +1,290 @@
/**
* 会话数据映射器
* 负责 Conversation 模型与 API 响应、数据库记录之间的转换
*/
import {
ConversationModel,
ConversationType,
UserModel,
GroupModel,
MessageModel,
} from '../models';
import type {
ConversationResponse,
ConversationDetailResponse,
UserDTO,
GroupResponse,
MessageResponse,
} from '../../types/dto';
import { MessageMapper } from './MessageMapper';
// 数据库会话记录类型
export interface ConversationDbRecord {
id: string;
participantId: string;
lastMessageId: string | null;
lastSeq: number;
unreadCount: number;
createdAt: string;
updatedAt: string;
}
// 缓存数据类型
export interface ConversationCacheData {
id: string;
type: string;
is_pinned?: boolean;
last_seq?: number;
last_message?: any;
last_message_at?: string;
unread_count?: number;
participants?: any[];
my_last_read_seq?: number;
other_last_read_seq?: number;
group?: any;
created_at?: string;
updated_at?: string;
}
export class ConversationMapper {
/**
* 将 API 列表响应转换为应用模型
*/
static fromApiResponse(response: ConversationResponse): ConversationModel {
return {
id: String(response.id || ''),
type: (response.type as ConversationType) || 'private',
participantId: this.extractParticipantId(response),
lastMessageId: response.last_message?.id,
lastMessage: response.last_message
? MessageMapper.fromApiResponse(response.last_message)
: undefined,
lastSeq: response.last_seq || 0,
myLastReadSeq: response.my_last_read_seq || 0,
otherLastReadSeq: response.other_last_read_seq || 0,
unreadCount: response.unread_count || 0,
isPinned: response.is_pinned || false,
participants: response.participants?.map(p => this.mapUserFromApi(p)),
group: response.group ? this.mapGroupFromApi(response.group) : undefined,
createdAt: new Date(response.created_at || Date.now()),
updatedAt: new Date(response.updated_at || Date.now()),
};
}
/**
* 将 API 详情响应转换为应用模型
*/
static fromDetailApiResponse(response: ConversationDetailResponse): ConversationModel {
return {
id: String(response.id || ''),
type: (response.type as ConversationType) || 'private',
participantId: this.extractParticipantIdFromDetail(response),
lastMessageId: response.last_message?.id,
lastMessage: response.last_message
? MessageMapper.fromApiResponse(response.last_message)
: undefined,
lastSeq: response.last_seq || 0,
myLastReadSeq: response.my_last_read_seq || 0,
otherLastReadSeq: response.other_last_read_seq || 0,
unreadCount: response.unread_count || 0,
isPinned: response.is_pinned || false,
participants: response.participants?.map(p => this.mapUserFromApi(p)),
group: response.group ? this.mapGroupFromApi(response.group) : undefined,
createdAt: new Date(response.created_at || Date.now()),
updatedAt: new Date(response.updated_at || Date.now()),
};
}
/**
* 将 API 响应数组转换为应用模型数组
*/
static fromApiResponseList(responses: ConversationResponse[]): ConversationModel[] {
return responses.map(r => this.fromApiResponse(r));
}
/**
* 从缓存数据转换为应用模型
*/
static fromCacheData(id: string, cached: ConversationCacheData): ConversationModel {
return {
id: String(cached?.id || id),
type: (cached?.type as ConversationType) || 'private',
participantId: '',
lastSeq: Number(cached?.last_seq || 0),
lastMessage: cached?.last_message
? MessageMapper.fromApiResponse(cached.last_message)
: undefined,
lastMessageId: cached?.last_message?.id,
myLastReadSeq: Number(cached?.my_last_read_seq || 0),
otherLastReadSeq: Number(cached?.other_last_read_seq || 0),
unreadCount: Number(cached?.unread_count || 0),
isPinned: Boolean(cached?.is_pinned),
participants: Array.isArray(cached?.participants)
? cached.participants.map(p => this.mapUserFromApi(p))
: [],
group: cached?.group ? this.mapGroupFromApi(cached.group) : undefined,
createdAt: new Date(cached?.created_at || Date.now()),
updatedAt: new Date(cached?.updated_at || Date.now()),
};
}
/**
* 将数据库记录转换为应用模型
*/
static fromDbRecord(record: ConversationDbRecord): ConversationModel {
return {
id: record.id,
type: 'private', // 数据库中不存储类型,需要额外查询
participantId: record.participantId,
lastMessageId: record.lastMessageId || undefined,
lastSeq: record.lastSeq,
myLastReadSeq: 0,
otherLastReadSeq: 0,
unreadCount: record.unreadCount,
isPinned: false,
createdAt: new Date(record.createdAt),
updatedAt: new Date(record.updatedAt),
};
}
/**
* 将应用模型转换为数据库记录
*/
static toDbRecord(model: ConversationModel): ConversationDbRecord {
return {
id: model.id,
participantId: model.participantId,
lastMessageId: model.lastMessageId || null,
lastSeq: model.lastSeq,
unreadCount: model.unreadCount,
createdAt: model.createdAt.toISOString(),
updatedAt: model.updatedAt.toISOString(),
};
}
/**
* 将应用模型转换为缓存数据
*/
static toCacheData(model: ConversationModel): ConversationCacheData {
return {
id: model.id,
type: model.type,
is_pinned: model.isPinned,
last_seq: model.lastSeq,
last_message: model.lastMessage ? this.messageToCache(model.lastMessage) : undefined,
last_message_at: model.updatedAt.toISOString(),
unread_count: model.unreadCount,
participants: model.participants?.map(p => this.userToCache(p)),
my_last_read_seq: model.myLastReadSeq,
other_last_read_seq: model.otherLastReadSeq,
group: model.group ? this.groupToCache(model.group) : undefined,
created_at: model.createdAt.toISOString(),
updated_at: model.updatedAt.toISOString(),
};
}
/**
* 提取会话参与者 ID
*/
private static extractParticipantId(response: ConversationResponse): string {
if (response.participants && response.participants.length > 0) {
return String(response.participants[0].id || '');
}
return '';
}
/**
* 从详情响应提取参与者 ID
*/
private static extractParticipantIdFromDetail(response: ConversationDetailResponse): string {
if (response.participants && response.participants.length > 0) {
return String(response.participants[0].id || '');
}
return '';
}
/**
* 映射用户 API 响应
*/
private static mapUserFromApi(user: UserDTO): UserModel {
return {
id: String(user.id || ''),
username: user.username || '',
nickname: user.nickname,
avatar: user.avatar,
};
}
/**
* 映射群组 API 响应
*/
private static mapGroupFromApi(group: GroupResponse): GroupModel {
return {
id: String(group.id || ''),
name: group.name || '',
avatar: group.avatar,
description: group.description,
announcement: group.announcement,
ownerId: String(group.owner_id || ''),
memberCount: group.member_count || 0,
maxMemberCount: group.max_member_count || 500,
joinType: group.join_type || 'approval',
isMuted: group.mute_all || false,
createdAt: new Date(group.created_at || Date.now()),
updatedAt: new Date(group.updated_at || Date.now()),
};
}
/**
* 消息模型转缓存格式
*/
private static messageToCache(message: MessageModel): any {
return {
id: message.id,
conversation_id: message.conversationId,
sender_id: message.senderId,
content: message.content,
message_type: message.type,
is_read: message.isRead,
created_at: message.createdAt.toISOString(),
seq: message.seq,
status: message.status,
segments: message.segments,
reply_to_id: message.replyToId,
sender: message.sender ? this.userToCache(message.sender) : undefined,
};
}
/**
* 用户模型转缓存格式
*/
private static userToCache(user: UserModel): any {
return {
id: user.id,
username: user.username,
nickname: user.nickname,
avatar: user.avatar,
};
}
/**
* 群组模型转缓存格式
*/
private static groupToCache(group: GroupModel): any {
return {
id: group.id,
name: group.name,
avatar: group.avatar,
description: group.description,
announcement: group.announcement,
owner_id: group.ownerId,
member_count: group.memberCount,
max_member_count: group.maxMemberCount,
join_type: group.joinType,
mute_all: group.isMuted,
created_at: group.createdAt.toISOString(),
updated_at: group.updatedAt.toISOString(),
};
}
}

View File

@@ -0,0 +1,183 @@
/**
* 消息数据映射器
* 负责 Message 模型与 API 响应、数据库记录之间的转换
*/
import {
MessageModel,
MessageSegment,
UserModel,
} from '../models';
import type {
MessageResponse,
UserDTO,
} from '../../types/dto';
// 数据库消息记录类型
export interface MessageDbRecord {
id: string;
conversationId: string;
senderId: string;
content: string | null;
type: string;
isRead: number;
createdAt: string;
seq: number;
status: string;
segments: string | null;
}
export class MessageMapper {
/**
* 将 API 响应转换为应用模型
*/
static fromApiResponse(response: MessageResponse): MessageModel {
return {
id: String(response.id || ''),
conversationId: String(response.conversation_id || ''),
senderId: String(response.sender_id || ''),
content: response.content || '',
type: response.message_type || 'text',
isRead: response.is_read || false,
createdAt: new Date(response.created_at || Date.now()),
seq: response.seq || 0,
status: response.status || 'normal',
segments: response.segments || [],
replyToId: response.reply_to_id,
sender: response.sender ? this.mapSenderFromApi(response.sender) : undefined,
};
}
/**
* 将 API 响应数组转换为应用模型数组
*/
static fromApiResponseList(responses: MessageResponse[]): MessageModel[] {
return responses.map(r => this.fromApiResponse(r));
}
/**
* 将数据库记录转换为应用模型
*/
static fromDbRecord(record: MessageDbRecord): MessageModel {
return {
id: record.id,
conversationId: record.conversationId,
senderId: record.senderId,
content: record.content || undefined,
type: record.type || 'text',
isRead: record.isRead === 1,
createdAt: new Date(record.createdAt),
seq: record.seq || 0,
status: record.status || 'normal',
segments: record.segments ? this.safeParseJson<MessageSegment[]>(record.segments) : undefined,
};
}
/**
* 将数据库记录数组转换为应用模型数组
*/
static fromDbRecordList(records: MessageDbRecord[]): MessageModel[] {
return records.map(r => this.fromDbRecord(r));
}
/**
* 将应用模型转换为数据库记录
*/
static toDbRecord(model: MessageModel): MessageDbRecord {
return {
id: model.id,
conversationId: model.conversationId,
senderId: model.senderId,
content: model.content || null,
type: model.type,
isRead: model.isRead ? 1 : 0,
createdAt: model.createdAt.toISOString(),
seq: model.seq,
status: model.status,
segments: model.segments ? JSON.stringify(model.segments) : null,
};
}
/**
* 将应用模型转换为 API 请求数据
*/
static toApiRequest(model: Partial<MessageModel>): Record<string, any> {
const request: Record<string, any> = {};
if (model.segments) {
request.segments = model.segments;
}
if (model.content) {
request.content = model.content;
}
if (model.type) {
request.message_type = model.type;
}
if (model.replyToId) {
request.reply_to_id = model.replyToId;
}
return request;
}
/**
* 创建发送消息请求体
*/
static createSendRequest(
detailType: 'private' | 'group',
segments: MessageSegment[],
replyToId?: string
): Record<string, any> {
const body: Record<string, any> = {
detail_type: detailType,
segments,
};
if (replyToId) {
body.reply_to_id = replyToId;
}
return body;
}
/**
* 创建文本消息段
*/
static createTextSegment(text: string): MessageSegment {
return {
type: 'text',
data: { text },
};
}
/**
* 创建图片消息段
*/
static createImageSegment(url: string): MessageSegment {
return {
type: 'image',
data: { url },
};
}
/**
* 安全解析 JSON
*/
private static safeParseJson<T>(value: string): T | undefined {
try {
return JSON.parse(value) as T;
} catch {
return undefined;
}
}
/**
* 映射发送者信息
*/
private static mapSenderFromApi(sender: UserDTO): UserModel {
return {
id: String(sender.id || ''),
username: sender.username || '',
nickname: sender.nickname,
avatar: sender.avatar,
};
}
}

View File

@@ -0,0 +1,123 @@
/**
* 帖子数据映射器
* 负责 Post 模型与 API 响应之间的转换
*/
import { PostModel, UserModel } from '../models';
import type { Post } from '../../types';
export class PostMapper {
/**
* 将 API 响应转换为应用模型
*/
static fromApiResponse(response: Post): PostModel {
return {
id: String(response.id || ''),
authorId: String(response.author_id || ''),
author: response.author ? this.mapAuthorFromApi(response.author) : undefined,
title: response.title || '',
content: response.content || '',
images: response.images || [],
likeCount: response.like_count || 0,
commentCount: response.comment_count || 0,
shareCount: response.share_count || 0,
viewCount: response.view_count || 0,
favoriteCount: response.favorite_count || 0,
isLiked: response.is_liked || false,
isFavorited: response.is_favorited || false,
isTop: response.is_top || false,
status: response.status || 'published',
communityId: response.community_id,
tags: response.tags || [],
createdAt: new Date(response.created_at || Date.now()),
updatedAt: new Date(response.updated_at || Date.now()),
};
}
/**
* 将 API 响应数组转换为应用模型数组
*/
static fromApiResponseList(responses: Post[]): PostModel[] {
return responses.map(r => this.fromApiResponse(r));
}
/**
* 将应用模型转换为 API 请求数据
*/
static toApiRequest(model: Partial<PostModel>): Record<string, any> {
const request: Record<string, any> = {};
if (model.title !== undefined) {
request.title = model.title;
}
if (model.content !== undefined) {
request.content = model.content;
}
if (model.images !== undefined) {
request.images = model.images;
}
if (model.communityId !== undefined) {
request.community_id = model.communityId;
}
if (model.tags !== undefined) {
request.tags = model.tags;
}
return request;
}
/**
* 创建帖子请求
*/
static createPostRequest(
title: string,
content: string,
images?: string[],
communityId?: string
): Record<string, any> {
const request: Record<string, any> = {
title,
content,
};
if (images && images.length > 0) {
request.images = images;
}
if (communityId) {
request.community_id = communityId;
}
return request;
}
/**
* 更新帖子请求
*/
static updatePostRequest(
title?: string,
content?: string,
images?: string[]
): Record<string, any> {
const request: Record<string, any> = {};
if (title !== undefined) {
request.title = title;
}
if (content !== undefined) {
request.content = content;
}
if (images !== undefined) {
request.images = images;
}
return request;
}
/**
* 映射作者信息
*/
private static mapAuthorFromApi(author: any): UserModel {
return {
id: String(author.id || ''),
username: author.username || '',
nickname: author.nickname,
avatar: author.avatar,
};
}
}

View File

@@ -0,0 +1,168 @@
/**
* 用户数据映射器
* 负责 User 模型与 API 响应、数据库记录之间的转换
*/
import { UserModel } from '../models';
import type { UserDTO, User } from '../../types/dto';
// 数据库用户记录类型
export interface UserDbRecord {
id: string;
data: string;
updatedAt: string;
}
export class UserMapper {
/**
* 将 API DTO 转换为应用模型
*/
static fromDTO(dto: UserDTO): UserModel {
return {
id: String(dto.id || ''),
username: dto.username || '',
nickname: dto.nickname,
avatar: dto.avatar,
bio: dto.bio,
website: dto.website,
location: dto.location,
email: dto.email,
phone: dto.phone,
followersCount: dto.followers_count,
followingCount: dto.following_count,
postsCount: dto.posts_count,
isFollowing: dto.is_following,
isBlocked: dto.is_blocked,
createdAt: dto.created_at ? new Date(dto.created_at) : undefined,
updatedAt: dto.updated_at ? new Date(dto.updated_at) : undefined,
};
}
/**
* 将 User 类型转换为应用模型
*/
static fromUser(user: User): UserModel {
return {
id: String(user.id || ''),
username: user.username || '',
nickname: user.nickname,
avatar: user.avatar,
bio: user.bio,
website: user.website,
location: user.location,
email: user.email,
phone: user.phone,
followersCount: user.followers_count,
followingCount: user.following_count,
postsCount: user.posts_count,
isFollowing: user.is_following,
isBlocked: user.is_blocked,
createdAt: user.created_at ? new Date(user.created_at) : undefined,
updatedAt: user.updated_at ? new Date(user.updated_at) : undefined,
};
}
/**
* 将 DTO 数组转换为应用模型数组
*/
static fromDTOList(dtos: UserDTO[]): UserModel[] {
return dtos.map(dto => this.fromDTO(dto));
}
/**
* 从数据库记录转换为应用模型
*/
static fromDbRecord(record: UserDbRecord): UserModel | null {
try {
const data = JSON.parse(record.data) as UserDTO;
return this.fromDTO(data);
} catch {
return null;
}
}
/**
* 将应用模型转换为数据库记录
*/
static toDbRecord(model: UserModel): UserDbRecord {
const dto: UserDTO = {
id: model.id,
username: model.username,
nickname: model.nickname,
avatar: model.avatar,
bio: model.bio,
website: model.website,
location: model.location,
email: model.email,
phone: model.phone,
followers_count: model.followersCount,
following_count: model.followingCount,
posts_count: model.postsCount,
is_following: model.isFollowing,
is_blocked: model.isBlocked,
created_at: model.createdAt?.toISOString(),
updated_at: model.updatedAt?.toISOString(),
};
return {
id: model.id,
data: JSON.stringify(dto),
updatedAt: new Date().toISOString(),
};
}
/**
* 将应用模型转换为 API 请求数据
*/
static toApiRequest(model: Partial<UserModel>): Record<string, any> {
const request: Record<string, any> = {};
if (model.nickname !== undefined) {
request.nickname = model.nickname;
}
if (model.avatar !== undefined) {
request.avatar = model.avatar;
}
if (model.bio !== undefined) {
request.bio = model.bio;
}
if (model.website !== undefined) {
request.website = model.website;
}
if (model.location !== undefined) {
request.location = model.location;
}
if (model.phone !== undefined) {
request.phone = model.phone;
}
if (model.email !== undefined) {
request.email = model.email;
}
return request;
}
/**
* 将应用模型转换为 DTO
*/
static toDTO(model: UserModel): UserDTO {
return {
id: model.id,
username: model.username,
nickname: model.nickname,
avatar: model.avatar,
bio: model.bio,
website: model.website,
location: model.location,
email: model.email,
phone: model.phone,
followers_count: model.followersCount,
following_count: model.followingCount,
posts_count: model.postsCount,
is_following: model.isFollowing,
is_blocked: model.isBlocked,
created_at: model.createdAt?.toISOString(),
updated_at: model.updatedAt?.toISOString(),
};
}
}

View File

@@ -0,0 +1,9 @@
/**
* 映射器导出
* 统一导出所有数据映射器
*/
export * from './MessageMapper';
export * from './ConversationMapper';
export * from './UserMapper';
export * from './PostMapper';

153
src/data/models/index.ts Normal file
View File

@@ -0,0 +1,153 @@
/**
* Repository 层模型定义
* 定义应用内部使用的数据模型,与 API 响应和数据库结构解耦
*/
// ==================== 消息模型 ====================
export interface MessageSegment {
type: string;
data: Record<string, any>;
}
export interface MessageModel {
id: string;
conversationId: string;
senderId: string;
content?: string;
type: 'text' | 'image' | 'file' | 'system' | string;
isRead: boolean;
createdAt: Date;
seq: number;
status: 'normal' | 'recalled' | 'deleted' | string;
segments?: MessageSegment[];
replyToId?: string;
sender?: UserModel;
}
// ==================== 会话模型 ====================
export type ConversationType = 'private' | 'group';
export interface ConversationModel {
id: string;
type: ConversationType;
participantId: string;
lastMessageId?: string;
lastMessage?: MessageModel;
lastSeq: number;
myLastReadSeq: number;
otherLastReadSeq: number;
unreadCount: number;
isPinned: boolean;
participants?: UserModel[];
group?: GroupModel;
createdAt: Date;
updatedAt: Date;
}
// ==================== 用户模型 ====================
export interface UserModel {
id: string;
username: string;
nickname?: string;
avatar?: string;
bio?: string;
website?: string;
location?: string;
email?: string;
phone?: string;
followersCount?: number;
followingCount?: number;
postsCount?: number;
isFollowing?: boolean;
isBlocked?: boolean;
createdAt?: Date;
updatedAt?: Date;
}
// ==================== 帖子模型 ====================
export interface PostModel {
id: string;
authorId: string;
author?: UserModel;
title: string;
content: string;
images?: string[];
likeCount: number;
commentCount: number;
shareCount: number;
viewCount: number;
favoriteCount: number;
isLiked: boolean;
isFavorited: boolean;
isTop: boolean;
status: 'published' | 'draft' | 'deleted';
communityId?: string;
tags?: string[];
createdAt: Date;
updatedAt: Date;
}
// ==================== 群组模型 ====================
export type GroupMemberRole = 'owner' | 'admin' | 'member';
export type GroupJoinType = 'anyone' | 'approval' | 'invite';
export interface GroupMemberModel {
userId: string;
user?: UserModel;
role: GroupMemberRole;
nickname?: string;
joinTime: Date;
muteUntil?: Date;
isMuted?: boolean;
}
export interface GroupModel {
id: string;
name: string;
avatar?: string;
description?: string;
announcement?: string;
ownerId: string;
owner?: UserModel;
memberCount: number;
maxMemberCount: number;
joinType: GroupJoinType;
isMuted: boolean;
createdAt: Date;
updatedAt: Date;
}
// ==================== 分页模型 ====================
export interface PaginatedResult<T> {
list: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
hasMore: boolean;
}
// ==================== 通用操作结果 ====================
export interface OperationResult<T = void> {
success: boolean;
data?: T;
error?: string;
code?: string;
}
// ==================== 同步状态模型 ====================
export interface SyncStatus {
entityType: string;
entityId: string;
lastSyncedAt: Date;
syncVersion: number;
pendingChanges: boolean;
}

View File

@@ -0,0 +1,220 @@
/**
* MessageRepository - 消息仓库实现
* 封装所有SQLite数据库操作不依赖任何UI或状态管理
*/
import {
saveMessage as dbSaveMessage,
saveMessagesBatch as dbSaveMessagesBatch,
getMessagesByConversation as dbGetMessagesByConversation,
getMaxSeq as dbGetMaxSeq,
getMinSeq as dbGetMinSeq,
getMessagesBeforeSeq as dbGetMessagesBeforeSeq,
markConversationAsRead as dbMarkConversationAsRead,
updateConversationCacheUnreadCount as dbUpdateConversationCacheUnreadCount,
getUserCache as dbGetUserCache,
saveUserCache as dbSaveUserCache,
deleteConversation as dbDeleteConversation,
updateMessageStatus as dbUpdateMessageStatus,
CachedMessage,
} from '../../services/database';
import { Message, Conversation, createMessage, createConversation } from '../../core/entities/Message';
// 数据库消息到领域实体的转换
const cachedMessageToMessage = (cached: CachedMessage): Message => ({
id: cached.id,
conversationId: cached.conversationId,
senderId: cached.senderId,
seq: cached.seq,
segments: cached.segments || [],
createdAt: cached.createdAt,
status: cached.status as Message['status'],
});
// 领域实体到数据库消息的转换
const messageToCachedMessage = (message: Message, isRead: boolean): CachedMessage => ({
id: message.id,
conversationId: message.conversationId,
senderId: message.senderId,
content: message.segments
.filter((s) => s.type === 'text')
.map((s) => s.data?.text || '')
.join(''),
type: message.segments.find((s) => s.type === 'image') ? 'image' : 'text',
isRead,
createdAt: message.createdAt,
seq: message.seq,
status: message.status,
segments: message.segments,
});
class MessageRepository {
// ==================== 消息操作 ====================
/**
* 保存单条消息
*/
async saveMessage(message: Message, isRead: boolean): Promise<void> {
try {
const cachedMessage = messageToCachedMessage(message, isRead);
await dbSaveMessage(cachedMessage);
} catch (error) {
console.error('[MessageRepository] 保存消息失败:', error);
throw error;
}
}
/**
* 批量保存消息
*/
async saveMessages(messages: Message[], isRead: boolean): Promise<void> {
if (!messages || messages.length === 0) return;
try {
const cachedMessages = messages.map((m) => messageToCachedMessage(m, isRead));
await dbSaveMessagesBatch(cachedMessages);
} catch (error) {
console.error('[MessageRepository] 批量保存消息失败:', error);
throw error;
}
}
/**
* 获取会话的消息
*/
async getMessagesByConversation(
conversationId: string,
limit: number = 20
): Promise<Message[]> {
try {
const cachedMessages = await dbGetMessagesByConversation(conversationId, limit);
return cachedMessages.map(cachedMessageToMessage);
} catch (error) {
console.error('[MessageRepository] 获取消息失败:', error);
return [];
}
}
/**
* 获取会话的最大消息序号
*/
async getMaxSeq(conversationId: string): Promise<number> {
try {
return await dbGetMaxSeq(conversationId);
} catch (error) {
console.error('[MessageRepository] 获取最大序号失败:', error);
return 0;
}
}
/**
* 获取会话的最小消息序号
*/
async getMinSeq(conversationId: string): Promise<number> {
try {
return await dbGetMinSeq(conversationId);
} catch (error) {
console.error('[MessageRepository] 获取最小序号失败:', error);
return 0;
}
}
/**
* 获取指定seq之前的历史消息
*/
async getMessagesBeforeSeq(
conversationId: string,
beforeSeq: number,
limit: number = 20
): Promise<Message[]> {
try {
const cachedMessages = await dbGetMessagesBeforeSeq(conversationId, beforeSeq, limit);
return cachedMessages.map(cachedMessageToMessage);
} catch (error) {
console.error('[MessageRepository] 获取历史消息失败:', error);
return [];
}
}
/**
* 标记会话的所有消息为已读
*/
async markConversationAsRead(conversationId: string): Promise<void> {
try {
await dbMarkConversationAsRead(conversationId);
} catch (error) {
console.error('[MessageRepository] 标记会话已读失败:', error);
throw error;
}
}
/**
* 更新会话缓存的未读数
*/
async updateConversationUnreadCount(
conversationId: string,
count: number
): Promise<void> {
try {
await dbUpdateConversationCacheUnreadCount(conversationId, count);
} catch (error) {
console.error('[MessageRepository] 更新会话未读数失败:', error);
}
}
/**
* 更新消息状态(如撤回)
*/
async updateMessageStatus(
messageId: string,
status: string,
clearContent: boolean = false
): Promise<void> {
try {
await dbUpdateMessageStatus(messageId, status, clearContent);
} catch (error) {
console.error('[MessageRepository] 更新消息状态失败:', error);
throw error;
}
}
/**
* 删除会话及其所有消息
*/
async deleteConversation(conversationId: string): Promise<void> {
try {
await dbDeleteConversation(conversationId);
} catch (error) {
console.error('[MessageRepository] 删除会话失败:', error);
throw error;
}
}
// ==================== 用户缓存操作 ====================
/**
* 获取用户缓存
*/
async getUserCache(userId: string): Promise<any | null> {
try {
return await dbGetUserCache(userId);
} catch (error) {
console.error('[MessageRepository] 获取用户缓存失败:', error);
return null;
}
}
/**
* 保存用户缓存
*/
async saveUserCache(user: any): Promise<void> {
try {
await dbSaveUserCache(user);
} catch (error) {
console.error('[MessageRepository] 保存用户缓存失败:', error);
}
}
}
export const messageRepository = new MessageRepository();
export default messageRepository;

View File

@@ -0,0 +1,93 @@
/**
* 消息 Repository 接口
* 定义消息数据访问的抽象
*/
import type { Message, Conversation, ConversationType } from '../../types/dto';
export interface IMessageRepository {
// ==================== 消息操作 ====================
/**
* 获取会话的消息列表
*/
getMessages(
conversationId: string,
options?: {
beforeSeq?: number;
afterSeq?: number;
limit?: number;
}
): Promise<Message[]>;
/**
* 保存消息
*/
saveMessage(message: Message): Promise<void>;
/**
* 批量保存消息
*/
saveMessages(messages: Message[]): Promise<void>;
/**
* 更新消息状态
*/
updateMessageStatus(
messageId: string,
status: 'normal' | 'recalled' | 'deleted'
): Promise<void>;
/**
* 标记消息为已读
*/
markAsRead(messageIds: string[]): Promise<void>;
/**
* 获取未读消息数
*/
getUnreadCount(conversationId: string): Promise<number>;
// ==================== 会话操作 ====================
/**
* 获取会话列表
*/
getConversations(): Promise<Conversation[]>;
/**
* 获取单个会话
*/
getConversation(conversationId: string): Promise<Conversation | null>;
/**
* 保存会话
*/
saveConversation(conversation: Conversation): Promise<void>;
/**
* 更新会话未读数
*/
updateUnreadCount(conversationId: string, count: number): Promise<void>;
/**
* 更新会话最后消息
*/
updateLastMessage(
conversationId: string,
messageId: string,
seq: number
): Promise<void>;
// ==================== 同步操作 ====================
/**
* 从服务器同步消息
*/
syncMessages(conversationId: string, lastSeq: number): Promise<Message[]>;
/**
* 获取服务器最新消息序列号
*/
getServerLastSeq(conversationId: string): Promise<number>;
}