import { postRepository } from '../../data/repositories/PostRepository'; import type { Post } from '../../core/entities/Post'; import type { GetPostsParams, CreatePostData, UpdatePostData, PostsResult, SearchPostsParams, } from '../../data/repositories/interfaces/IPostRepository'; import { PaginationStateManager } from '../../infrastructure/pagination/PaginationStateManager'; import type { PaginationState } from '../../infrastructure/pagination/types'; import { usePostListStore, type PostsState } from '../../stores/post/postListStore'; import { mergePostListWithFastPath, mergeRefreshWindow } from './postMerge'; export type { PostsState } from '../../stores/post/postListStore'; export interface PostDetailState { post: Post | null; isLoading: boolean; error: string | null; } const DEFAULT_PAGE_SIZE = 20; const MERGE_PERF_WARN_THRESHOLD_MS = 12; class PostSyncService { private static instance: PostSyncService; private paginationManager: PaginationStateManager; private currentUserId: string | null = null; private constructor() { this.paginationManager = new PaginationStateManager(); } static getInstance(): PostSyncService { if (!PostSyncService.instance) { PostSyncService.instance = new PostSyncService(); } return PostSyncService.instance; } initialize(currentUserId: string | null = null): void { this.currentUserId = currentUserId; } destroy(): void { usePostListStore.getState().clearAllStates(); this.paginationManager.clear(); this.currentUserId = null; } getPostsState(key: string = 'default'): PostsState { return usePostListStore.getState().getPostsState(key); } getPostDetailState(postId: string): PostDetailState { return usePostListStore.getState().getPostDetailState(postId); } applyShareCountUpdate(postId: string, sharesCount: number): void { usePostListStore.getState().applyShareCountUpdate(postId, sharesCount); } async fetchPosts(params?: GetPostsParams, key: string = 'default'): Promise { const queryParams: GetPostsParams = { pageSize: params?.pageSize || DEFAULT_PAGE_SIZE, ...params, cursor: params?.cursor !== undefined ? params.cursor : '', }; // 判断参数是否变化:参数变化(切换分区/排序/筛选)时直接清空旧数据, // 避免渲染期间残留上一个分区的帖子造成闪烁 const prevState = usePostListStore.getState().getPostsState(key); const paramsChanged = JSON.stringify(prevState.lastParams) !== JSON.stringify(queryParams); // 参数变化时立即写入空数据 + loading 状态,确保 UI 第一时间进入加载态 // 而不是先渲染上一个分区的旧帖子 usePostListStore.getState().updatePostsState(key, { isLoading: true, error: null, ...(paramsChanged ? { posts: [], cursor: null, currentPage: 1, hasMore: true } : {}), }); try { const result = await postRepository.getPosts(queryParams); const isCursorMode = result.nextCursor !== undefined && result.nextCursor !== null; const currentState = usePostListStore.getState().getPostsState(key); const incomingPosts = Array.isArray(result.posts) ? result.posts : []; const mergeStart = Date.now(); // 参数变化时直接替换;同参数刷新则合并以保留滚动位置上的帖子 const mergedPosts = paramsChanged ? incomingPosts : mergeRefreshWindow(currentState.posts, incomingPosts); const mergeCost = Date.now() - mergeStart; if (mergeCost >= MERGE_PERF_WARN_THRESHOLD_MS) { console.log('[PostPerf] fetchPosts merge cost', { key, costMs: mergeCost, prev: currentState.posts.length, next: incomingPosts.length }); } usePostListStore.getState().updatePostsState(key, { posts: mergedPosts, hasMore: result.hasMore, cursor: isCursorMode ? result.nextCursor : null, currentPage: isCursorMode ? undefined : 1, isLoading: false, lastLoadTime: Date.now(), lastParams: params, }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : '获取帖子列表失败'; usePostListStore.getState().updatePostsState(key, { isLoading: false, error: errorMessage }); throw error; } } async refreshPosts(key: string = 'default'): Promise { const state = usePostListStore.getState().getPostsState(key); usePostListStore.getState().updatePostsState(key, { isRefreshing: true, error: null }); try { const result = await postRepository.getPosts({ pageSize: DEFAULT_PAGE_SIZE, ...state.lastParams, cursor: state.lastParams?.cursor !== undefined ? state.lastParams.cursor : '', }); const isCursorMode = result.nextCursor !== undefined && result.nextCursor !== null; const incomingPosts = Array.isArray(result.posts) ? result.posts : []; const mergeStart = Date.now(); // refreshPosts 是刷新操作,应直接替换当前列表,避免旧数据残留 const mergedPosts = incomingPosts; const mergeCost = Date.now() - mergeStart; if (mergeCost >= MERGE_PERF_WARN_THRESHOLD_MS) { console.log('[PostPerf] refreshPosts merge cost', { key, costMs: mergeCost, prev: state.posts.length, next: incomingPosts.length }); } usePostListStore.getState().updatePostsState(key, { posts: mergedPosts, hasMore: result.hasMore, cursor: isCursorMode ? result.nextCursor : null, currentPage: isCursorMode ? undefined : 1, isRefreshing: false, lastLoadTime: Date.now(), }); this.paginationManager.reset(key); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : '刷新帖子列表失败'; usePostListStore.getState().updatePostsState(key, { isRefreshing: false, error: errorMessage }); throw error; } } async loadMorePosts(key: string = 'default'): Promise { const state = usePostListStore.getState().getPostsState(key); if (!state.hasMore) return { posts: [], hasMore: false }; if (state.isLoading) return { posts: [], hasMore: state.hasMore }; usePostListStore.getState().updatePostsState(key, { isLoading: true, error: null }); try { const useCursorMode = state.cursor !== null && state.cursor !== undefined; const base = { ...(state.lastParams ?? {}) } as GetPostsParams; let queryParams: GetPostsParams; if (useCursorMode) { delete base.page; queryParams = { ...base, pageSize: DEFAULT_PAGE_SIZE, cursor: state.cursor! }; } else { const nextPage = (state.currentPage || 1) + 1; delete base.cursor; queryParams = { ...base, pageSize: DEFAULT_PAGE_SIZE, page: nextPage }; } const result = await postRepository.getPosts(queryParams); const incomingPosts = Array.isArray(result.posts) ? result.posts : []; const mergeStart = Date.now(); const newPosts = mergePostListWithFastPath(state.posts, incomingPosts); const mergeCost = Date.now() - mergeStart; if (mergeCost >= MERGE_PERF_WARN_THRESHOLD_MS) { console.log('[PostPerf] loadMore merge cost', { key, costMs: mergeCost, prev: state.posts.length, incoming: incomingPosts.length, merged: newPosts.length }); } const responseIsCursorMode = result.nextCursor !== undefined && result.nextCursor !== null; usePostListStore.getState().updatePostsState(key, { posts: newPosts, hasMore: result.hasMore, cursor: responseIsCursorMode ? result.nextCursor : null, currentPage: useCursorMode ? state.currentPage : ((state.currentPage || 1) + 1), isLoading: false, lastLoadTime: Date.now(), }); return { posts: incomingPosts, hasMore: result.hasMore, nextCursor: result.nextCursor }; } catch (error) { const errorMessage = error instanceof Error ? error.message : '加载更多帖子失败'; console.error('[PostSyncService] Load more error:', errorMessage); usePostListStore.getState().updatePostsState(key, { isLoading: false, error: errorMessage }); throw error; } } async fetchPostById(id: string): Promise { usePostListStore.getState().updatePostDetailState(id, { isLoading: true, error: null }); try { const post = await postRepository.getPostById(id); usePostListStore.getState().updatePostDetailState(id, { post, isLoading: false }); return post; } catch (error) { const errorMessage = error instanceof Error ? error.message : '获取帖子详情失败'; usePostListStore.getState().updatePostDetailState(id, { isLoading: false, error: errorMessage }); throw error; } } async createPost(data: CreatePostData): Promise { const post = await postRepository.createPost(data); const store = usePostListStore.getState(); const newMap = new Map(store.postsStateMap); for (const [key, postsState] of newMap) { newMap.set(key, { ...postsState, posts: [post, ...postsState.posts] }); } usePostListStore.setState({ postsStateMap: newMap }); return post; } async updatePost(id: string, data: UpdatePostData): Promise { const post = await postRepository.updatePost(id, data); usePostListStore.getState().updatePostDetailState(id, { post }); usePostListStore.getState().updatePostInLists(id, post); return post; } async deletePost(id: string): Promise { await postRepository.deletePost(id); usePostListStore.getState().clearPostDetailState(id); usePostListStore.getState().removePostFromLists(id); } async likePost(id: string): Promise { const post = await postRepository.likePost(id); usePostListStore.getState().updatePostDetailState(id, { post }); usePostListStore.getState().updatePostInLists(id, post); return post; } async unlikePost(id: string): Promise { const post = await postRepository.unlikePost(id); usePostListStore.getState().updatePostDetailState(id, { post }); usePostListStore.getState().updatePostInLists(id, post); return post; } async favoritePost(id: string): Promise { const post = await postRepository.favoritePost(id); usePostListStore.getState().updatePostDetailState(id, { post }); usePostListStore.getState().updatePostInLists(id, post); return post; } async unfavoritePost(id: string): Promise { const post = await postRepository.unfavoritePost(id); usePostListStore.getState().updatePostDetailState(id, { post }); usePostListStore.getState().updatePostInLists(id, post); return post; } async searchPosts(params: SearchPostsParams, key: string = 'search'): Promise { usePostListStore.getState().updatePostsState(key, { isLoading: true, error: null }); try { const result = await postRepository.searchPosts(params); usePostListStore.getState().updatePostsState(key, { posts: result.posts, hasMore: result.hasMore, cursor: result.nextCursor || null, isLoading: false, lastLoadTime: Date.now(), }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : '搜索帖子失败'; usePostListStore.getState().updatePostsState(key, { isLoading: false, error: errorMessage }); throw error; } } async fetchPostsByUser(userId: string, params?: GetPostsParams, key?: string): Promise { const listKey = key || `user_${userId}`; usePostListStore.getState().updatePostsState(listKey, { isLoading: true, error: null }); try { const result = await postRepository.getPostsByUser(userId, params); usePostListStore.getState().updatePostsState(listKey, { posts: result.posts, hasMore: result.hasMore, cursor: result.nextCursor || null, isLoading: false, lastLoadTime: Date.now(), }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : '获取用户帖子失败'; usePostListStore.getState().updatePostsState(listKey, { isLoading: false, error: errorMessage }); throw error; } } clearPostsState(key: string = 'default'): void { usePostListStore.getState().clearPostsState(key); this.paginationManager.reset(key); } clearPostDetailState(postId: string): void { usePostListStore.getState().clearPostDetailState(postId); } clearAllStates(): void { usePostListStore.getState().clearAllStates(); this.paginationManager.clear(); } getCurrentUserId(): string | null { return this.currentUserId; } setCurrentUserId(userId: string | null): void { this.currentUserId = userId; } getPaginationState(key: string): PaginationState { return this.paginationManager.getState(key); } resetPagination(key: string): void { this.paginationManager.reset(key); } isLoading(key: string = 'default'): boolean { return usePostListStore.getState().getPostsState(key).isLoading; } hasError(key: string = 'default'): boolean { return usePostListStore.getState().getPostsState(key).error !== null; } getError(key: string = 'default'): string | null { return usePostListStore.getState().getPostsState(key).error; } } export const postSyncService = PostSyncService.getInstance(); export { PostSyncService }; export default postSyncService;