refactor(post): consolidate post sync to PostSyncService and remove ProcessPostUseCase
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 3m28s
Frontend CI / ota-android (push) Successful in 10m25s
Frontend CI / build-android-apk (push) Successful in 55m22s

- Replace ProcessPostUseCase with new PostSyncService in src/services/post/
- Add postListStore for state management in src/stores/post/
- Remove deprecated ProcessPostUseCase and ProcessMessageUseCase files
- Delete architecture documentation files (ARCHITECTURE_REFACTOR_PLAN.md, architecture-comparison-report.md)
- Update all consumers (HomeScreen, PostDetailScreen, SearchScreen, useUserProfile, useDifferentialPosts)
- Simplify postService to only contain API layer methods
- Remove unused type exports from message stores

BREAKING CHANGE: ProcessPostUseCase and ProcessMessageUseCase have been removed.
Use postSyncService for post operations instead.
This commit is contained in:
lafay
2026-04-13 00:26:05 +08:00
parent 6610d2f173
commit 2adc9360a5
21 changed files with 679 additions and 2896 deletions

View File

@@ -28,9 +28,6 @@ export {
// MessageManager 新架构导出(已替换 conversationStore
export { messageManager, MessageManager, useMessageStore } from './message';
export type {
MessageEvent,
MessageEventType,
MessageSubscriber,
MessageManagerConversationListDeps,
} from './message';
export {

View File

@@ -29,9 +29,6 @@ import { useMessageStore, normalizeConversationId } from './store';
// 重新导出类型,保持兼容性
export type {
MessageEventType,
MessageEvent,
MessageSubscriber,
MessageManagerState,
ReadStateRecord,
MessageManagerConversationListDeps,

View File

@@ -23,15 +23,10 @@ export type { MessageState, MessageActions, MessageStore } from './store';
// ==================== 类型导出 ====================
// 从 types.ts 导出所有类型
export type {
MessageEventType,
MessageEvent,
MessageSubscriber,
MessageManagerState,
ReadStateRecord,
MessageManagerConversationListDeps,
HandleNewMessageOptions,
// 服务接口类型(保留向后兼容,但服务层已不再使用)
IMessageStateManager,
IMessageDeduplication,
IUserCacheService,
IReadReceiptManager,

View File

@@ -1,193 +1,48 @@
/**
* 消息管理类型定义模块
* 包含所有与消息管理相关的类型、接口和类型别名
*/
import type { ConversationResponse, MessageResponse, UserDTO } from '../../types/dto';
import type { IConversationListPagedSource } from '../conversationListSources';
// ==================== 事件类型 ====================
/**
* 消息事件类型枚举
* 定义了所有可能的消息事件类型
*/
export type MessageEventType =
| 'conversations_updated'
| 'conversations_loading'
| 'messages_updated'
| 'unread_count_updated'
| 'connection_changed'
| 'message_sent'
| 'message_read'
| 'message_received'
| 'message_recalled'
| 'typing_status'
| 'group_notice'
| 'error';
/**
* 消息事件接口
* 所有订阅者接收的事件对象结构
*/
export interface MessageEvent {
type: MessageEventType;
payload: any;
timestamp: number;
}
/**
* 消息订阅者类型
*/
export type MessageSubscriber = (event: MessageEvent) => void;
// ==================== 状态接口 ====================
/**
* 消息管理器内部状态接口
* 定义了所有需要管理的状态字段
*/
export interface MessageManagerState {
// 会话相关
conversations: Map<string, ConversationResponse>;
conversationList: ConversationResponse[];
// 消息相关 - 按会话ID存储
messagesMap: Map<string, MessageResponse[]>;
// 未读数
totalUnreadCount: number;
systemUnreadCount: number;
// 连接状态
isWSConnected: boolean;
// 当前活动会话ID用户正在查看的会话
currentConversationId: string | null;
// 加载状态
isLoadingConversations: boolean;
loadingMessagesSet: Set<string>; // 正在加载消息的会话ID集合
// 初始化状态
loadingMessagesSet: Set<string>;
isInitialized: boolean;
// 订阅者
subscribers: Set<MessageSubscriber>;
// 输入状态 - 按群组ID存储正在输入的用户ID列表
typingUsersMap: Map<string, string[]>;
// 当前用户的禁言状态 - 按群组ID存储
mutedStatusMap: Map<string, boolean>;
}
// ==================== 已读状态相关 ====================
/**
* 已读状态记录接口
* 用于跟踪已读操作的状态和保护机制
*/
export interface ReadStateRecord {
/** 标记已读的时间戳 */
timestamp: number;
/** 状态版本号,用于防止旧数据覆盖新数据 */
version: number;
/** 已上报的已读序号(用于去重) */
lastReadSeq: number;
/** 清除保护的定时器ID */
clearTimer?: ReturnType<typeof setTimeout>;
}
// ==================== 依赖注入接口 ====================
/**
* 可注入会话列表源,便于测试或替换实现
*/
export interface MessageManagerConversationListDeps {
remoteConversationListSource?: IConversationListPagedSource;
localConversationListSource?: IConversationListPagedSource;
/**
* 未传 remoteConversationListSource 时:默认 `cursor`/conversations/cursor
* `offset` 为常规页码(/conversations?page=&page_size=)。
*/
remoteListKind?: 'cursor' | 'offset';
/** 与 remoteListKind 搭配,默认与 CONVERSATION_LIST_PAGE_SIZE 一致 */
remoteListPageSize?: number;
}
// ==================== 消息处理选项 ====================
/**
* 处理新消息的选项
*/
export interface HandleNewMessageOptions {
/** 是否抑制未读数增加 */
suppressUnreadIncrement?: boolean;
/** 是否抑制震动 */
suppressVibration?: boolean;
/** 是否抑制会话更新 */
suppressConversationUpdates?: boolean;
/** 是否抑制自动标记已读 */
suppressAutoMarkAsRead?: boolean;
}
// ==================== 服务接口 ====================
/**
* 状态管理器接口
* 定义状态管理器需要实现的方法
*/
export interface IMessageStateManager {
// 获取状态
getState(): MessageManagerState;
getConversations(): ConversationResponse[];
getConversation(conversationId: string): ConversationResponse | null;
getMessages(conversationId: string): MessageResponse[];
getUnreadCount(): { total: number; system: number };
isConnected(): boolean;
isLoading(): boolean;
isLoadingMessages(conversationId: string): boolean;
getTypingUsers(groupId: string): string[];
isMuted(groupId: string): boolean;
getActiveConversation(): string | null;
// 设置状态
setConversations(conversations: Map<string, ConversationResponse>): void;
updateConversation(conversation: ConversationResponse): void;
removeConversation(conversationId: string): void;
setMessages(conversationId: string, messages: MessageResponse[]): void;
addMessage(conversationId: string, message: MessageResponse): void;
updateMessage(conversationId: string, messageId: string, updates: Partial<MessageResponse>): void;
setUnreadCount(total: number, system: number): void;
setSSEConnected(connected: boolean): void;
setCurrentConversation(conversationId: string | null): void;
setLoading(loading: boolean): void;
setLoadingMessages(conversationId: string, loading: boolean): void;
setTypingUsers(groupId: string, users: string[]): void;
setMutedStatus(groupId: string, isMuted: boolean): void;
setInitialized(initialized: boolean): void;
// 订阅机制
subscribe(subscriber: MessageSubscriber): () => void;
notifySubscribers(event: MessageEvent): void;
// 重置
reset(): void;
}
/**
* 消息去重服务接口
*/
export interface IMessageDeduplication {
isMessageProcessed(messageId: string): boolean;
markMessageAsProcessed(messageId: string): void;
cleanupProcessedMessageIds(): void;
}
/**
* 用户缓存服务接口
*/
export interface IUserCacheService {
getSenderInfo(userId: string): Promise<UserDTO | null>;
enrichMessagesWithSenderInfo(
@@ -197,9 +52,6 @@ export interface IUserCacheService {
): void;
}
/**
* 已读回执管理器接口
*/
export interface IReadReceiptManager {
markAsRead(conversationId: string, seq: number): Promise<void>;
markAllAsRead(): Promise<void>;
@@ -210,9 +62,6 @@ export interface IReadReceiptManager {
getPendingReadMap(): Map<string, ReadStateRecord>;
}
/**
* 会话操作接口
*/
export interface IConversationOperations {
createConversation(userId: string): Promise<ConversationResponse | null>;
updateConversation(conversationId: string, updates: Partial<ConversationResponse>): void;
@@ -225,9 +74,6 @@ export interface IConversationOperations {
decrementSystemUnreadCount(count?: number): void;
}
/**
* 消息发送服务接口
*/
export interface IMessageSendService {
sendMessage(
conversationId: string,
@@ -236,9 +82,6 @@ export interface IMessageSendService {
): Promise<MessageResponse | null>;
}
/**
* 消息同步服务接口
*/
export interface IMessageSyncService {
fetchConversations(forceRefresh?: boolean, source?: string): Promise<void>;
loadMoreConversations(): Promise<void>;
@@ -249,9 +92,6 @@ export interface IMessageSyncService {
canLoadMoreConversations(): boolean;
}
/**
* WebSocket 消息处理器接口
*/
export interface IWSMessageHandler {
connect(): void;
disconnect(): void;

View File

@@ -50,18 +50,10 @@ export {
// 重导出类型
export type {
// 事件类型
MessageEventType,
MessageEvent,
MessageSubscriber,
// 状态类型
MessageManagerState,
ReadStateRecord,
MessageManagerConversationListDeps,
HandleNewMessageOptions,
// 服务接口类型
IMessageDeduplication,
IUserCacheService,
IReadReceiptManager,

View File

@@ -2,7 +2,7 @@
* 帖子模块统一导出
*/
// ==================== Store ====================
// ==================== Store (PostManager) ====================
export {
usePostManagerStore,
type PostManagerState,
@@ -10,6 +10,14 @@ export {
type PostManagerStore,
} from './postStore';
// ==================== Store (ProcessPostUseCase) ====================
export {
usePostListStore,
type PostsState,
type PostDetailState,
type PostListStore,
} from './postListStore';
// ==================== Manager ====================
export { postManager, PostManager } from './PostManager';

View File

@@ -0,0 +1,185 @@
import { create } from 'zustand';
import type { Post } from '../../core/entities/Post';
export interface PostsState {
posts: Post[];
isLoading: boolean;
isRefreshing: boolean;
hasMore: boolean;
error: string | null;
cursor: string | null;
currentPage: number;
lastLoadTime: number | null;
lastParams?: any;
}
export interface PostDetailState {
post: Post | null;
isLoading: boolean;
error: string | null;
}
export interface PostListStoreState {
postsStateMap: Map<string, PostsState>;
postDetailStateMap: Map<string, PostDetailState>;
}
export interface PostListStoreActions {
getPostsState: (key: string) => PostsState;
getPostDetailState: (postId: string) => PostDetailState;
updatePostsState: (key: string, partial: Partial<PostsState>) => void;
updatePostDetailState: (postId: string, partial: Partial<PostDetailState>) => void;
updatePostInLists: (postId: string, updatedPost: Post) => void;
removePostFromLists: (postId: string) => void;
applyShareCountUpdate: (postId: string, sharesCount: number) => void;
clearPostsState: (key: string) => void;
clearPostDetailState: (postId: string) => void;
clearAllStates: () => void;
reset: () => void;
}
const DEFAULT_POSTS_STATE: PostsState = {
posts: [],
isLoading: false,
isRefreshing: false,
hasMore: true,
error: null,
cursor: null,
currentPage: 1,
lastLoadTime: null,
};
const DEFAULT_POST_DETAIL_STATE: PostDetailState = {
post: null,
isLoading: false,
error: null,
};
export type PostListStore = PostListStoreState & PostListStoreActions;
export const usePostListStore = create<PostListStore>((set, get) => ({
postsStateMap: new Map(),
postDetailStateMap: new Map(),
getPostsState: (key: string) => {
return get().postsStateMap.get(key) ?? { ...DEFAULT_POSTS_STATE };
},
getPostDetailState: (postId: string) => {
return get().postDetailStateMap.get(postId) ?? { ...DEFAULT_POST_DETAIL_STATE };
},
updatePostsState: (key: string, partial: Partial<PostsState>) => {
set(state => {
const current = state.postsStateMap.get(key) ?? { ...DEFAULT_POSTS_STATE };
const newMap = new Map(state.postsStateMap);
newMap.set(key, { ...current, ...partial });
return { postsStateMap: newMap };
});
},
updatePostDetailState: (postId: string, partial: Partial<PostDetailState>) => {
set(state => {
const current = state.postDetailStateMap.get(postId) ?? { ...DEFAULT_POST_DETAIL_STATE };
const newMap = new Map(state.postDetailStateMap);
newMap.set(postId, { ...current, ...partial });
return { postDetailStateMap: newMap };
});
},
updatePostInLists: (postId: string, updatedPost: Post) => {
set(state => {
const newMap = new Map(state.postsStateMap);
let changed = false;
for (const [key, postsState] of newMap) {
const index = postsState.posts.findIndex(p => p.id === postId);
if (index !== -1) {
const newPosts = [...postsState.posts];
newPosts[index] = updatedPost;
newMap.set(key, { ...postsState, posts: newPosts });
changed = true;
}
}
return changed ? { postsStateMap: newMap } : state;
});
},
removePostFromLists: (postId: string) => {
set(state => {
const newMap = new Map(state.postsStateMap);
let changed = false;
for (const [key, postsState] of newMap) {
const filtered = postsState.posts.filter(p => p.id !== postId);
if (filtered.length !== postsState.posts.length) {
newMap.set(key, { ...postsState, posts: filtered });
changed = true;
}
}
return changed ? { postsStateMap: newMap } : state;
});
},
applyShareCountUpdate: (postId: string, sharesCount: number) => {
set(state => {
const newPostsMap = new Map(state.postsStateMap);
let postsChanged = false;
for (const [key, postsState] of newPostsMap) {
const index = postsState.posts.findIndex(p => p.id === postId);
if (index !== -1) {
const newPosts = [...postsState.posts];
const cur = newPosts[index];
newPosts[index] = { ...cur, sharesCount, shares_count: sharesCount };
newPostsMap.set(key, { ...postsState, posts: newPosts });
postsChanged = true;
}
}
const newDetailMap = new Map(state.postDetailStateMap);
let detailChanged = false;
const detail = newDetailMap.get(postId);
if (detail?.post) {
newDetailMap.set(postId, {
...detail,
post: { ...detail.post, sharesCount, shares_count: sharesCount },
});
detailChanged = true;
}
if (!postsChanged && !detailChanged) return state;
return {
...(postsChanged ? { postsStateMap: newPostsMap } : {}),
...(detailChanged ? { postDetailStateMap: newDetailMap } : {}),
};
});
},
clearPostsState: (key: string) => {
set(state => {
const newMap = new Map(state.postsStateMap);
newMap.delete(key);
return { postsStateMap: newMap };
});
},
clearPostDetailState: (postId: string) => {
set(state => {
const newMap = new Map(state.postDetailStateMap);
newMap.delete(postId);
return { postDetailStateMap: newMap };
});
},
clearAllStates: () => {
set({
postsStateMap: new Map(),
postDetailStateMap: new Map(),
});
},
reset: () => {
set({
postsStateMap: new Map(),
postDetailStateMap: new Map(),
});
},
}));

View File

@@ -1,66 +1,40 @@
/**
* 帖子状态 Zustand Store
*/
import { create } from 'zustand';
import type { Post } from '../../types';
import { CacheEntry, createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
// ==================== 状态接口 ====================
export interface PostManagerState {
// 帖子列表缓存 (key: list:type:page:pageSize)
listsMap: Map<string, CacheEntry<Post[]>>;
// 帖子详情缓存
detailsMap: Map<string, CacheEntry<Post | null>>;
// 加载状态
loadingListKeys: Set<string>;
loadingDetailIds: Set<string>;
// 待处理请求(去重)
pendingRequests: Map<string, Promise<any>>;
}
export interface PostManagerActions {
// 获取状态
getPostsList: (type: string, page: number, pageSize: number) => Post[] | null;
getPostDetail: (postId: string) => Post | null;
isListExpired: (type: string, page: number, pageSize: number) => boolean;
isDetailExpired: (postId: string) => boolean;
isLoadingList: (type: string, page: number, pageSize: number) => boolean;
isLoadingDetail: (postId: string) => boolean;
// 设置状态
setPostsList: (type: string, page: number, pageSize: number, posts: Post[]) => void;
setPostDetail: (postId: string, post: Post | null) => void;
setLoadingList: (type: string, page: number, pageSize: number, loading: boolean) => void;
setLoadingDetail: (postId: string, loading: boolean) => void;
// 缓存管理
invalidateList: (type?: string, page?: number, pageSize?: number) => void;
invalidateDetail: (postId: string) => void;
invalidateAll: () => void;
// 请求去重
getPendingRequest: <T>(key: string) => Promise<T> | undefined;
setPendingRequest: <T>(key: string, request: Promise<T>) => void;
deletePendingRequest: (key: string) => void;
clearPendingRequests: () => void;
// 重置
reset: () => void;
}
// ==================== 辅助函数 ====================
function buildListKey(type: string, page: number, pageSize: number): string {
return `list:${type}:${page}:${pageSize}`;
}
// ==================== 初始状态 ====================
const initialState: PostManagerState = {
listsMap: new Map(),
detailsMap: new Map(),
@@ -69,16 +43,11 @@ const initialState: PostManagerState = {
pendingRequests: new Map(),
};
// ==================== Store 创建 ====================
export type PostManagerStore = PostManagerState & PostManagerActions;
export const usePostManagerStore = create<PostManagerStore>((set, get) => ({
// ==================== 初始状态 ====================
...initialState,
// ==================== 获取状态 ====================
getPostsList: (type: string, page: number, pageSize: number) => {
const key = buildListKey(type, page, pageSize);
const entry = get().listsMap.get(key);
@@ -116,8 +85,6 @@ export const usePostManagerStore = create<PostManagerStore>((set, get) => ({
return get().loadingDetailIds.has(postId);
},
// ==================== 设置状态 ====================
setPostsList: (type: string, page: number, pageSize: number, posts: Post[]) => {
const key = buildListKey(type, page, pageSize);
set(state => {
@@ -160,8 +127,6 @@ export const usePostManagerStore = create<PostManagerStore>((set, get) => ({
});
},
// ==================== 缓存管理 ====================
invalidateList: (type?: string, page?: number, pageSize?: number) => {
if (type !== undefined && page !== undefined && pageSize !== undefined) {
const key = buildListKey(type, page, pageSize);
@@ -191,8 +156,6 @@ export const usePostManagerStore = create<PostManagerStore>((set, get) => ({
});
},
// ==================== 请求去重 ====================
getPendingRequest: <T>(key: string) => {
return get().pendingRequests.get(key) as Promise<T> | undefined;
},
@@ -217,8 +180,6 @@ export const usePostManagerStore = create<PostManagerStore>((set, get) => ({
set({ pendingRequests: new Map() });
},
// ==================== 重置 ====================
reset: () => {
set({
...initialState,