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

@@ -0,0 +1,346 @@
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<PostsResult> {
const store = usePostListStore.getState();
store.updatePostsState(key, { isLoading: true, error: null });
try {
const queryParams: GetPostsParams = {
pageSize: params?.pageSize || DEFAULT_PAGE_SIZE,
...params,
cursor: params?.cursor !== undefined ? params.cursor : '',
};
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 = 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<PostsResult> {
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();
const mergedPosts = mergeRefreshWindow(state.posts, 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<PostsResult> {
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<Post | null> {
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<Post> {
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<Post> {
const post = await postRepository.updatePost(id, data);
usePostListStore.getState().updatePostDetailState(id, { post });
usePostListStore.getState().updatePostInLists(id, post);
return post;
}
async deletePost(id: string): Promise<void> {
await postRepository.deletePost(id);
usePostListStore.getState().clearPostDetailState(id);
usePostListStore.getState().removePostFromLists(id);
}
async likePost(id: string): Promise<Post> {
const post = await postRepository.likePost(id);
usePostListStore.getState().updatePostDetailState(id, { post });
usePostListStore.getState().updatePostInLists(id, post);
return post;
}
async unlikePost(id: string): Promise<Post> {
const post = await postRepository.unlikePost(id);
usePostListStore.getState().updatePostDetailState(id, { post });
usePostListStore.getState().updatePostInLists(id, post);
return post;
}
async favoritePost(id: string): Promise<Post> {
const post = await postRepository.favoritePost(id);
usePostListStore.getState().updatePostDetailState(id, { post });
usePostListStore.getState().updatePostInLists(id, post);
return post;
}
async unfavoritePost(id: string): Promise<Post> {
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<PostsResult> {
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<PostsResult> {
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;

View File

@@ -0,0 +1,66 @@
import type { Post } from '../../core/entities/Post';
export function getEntityId(item: unknown): string | null {
if (!item || typeof item !== 'object') return null;
const maybeId = (item as { id?: unknown }).id;
if (typeof maybeId === 'string' || typeof maybeId === 'number') return String(maybeId);
return null;
}
export function mergePostListWithFastPath(
previousInput: Post[] | null | undefined,
incomingInput: Post[] | null | undefined
): Post[] {
const previous = Array.isArray(previousInput) ? previousInput : [];
const incoming = Array.isArray(incomingInput) ? incomingInput : [];
if (incoming.length === 0) return previous;
if (previous.length === 0) return incoming;
const lastPrevId = getEntityId(previous[previous.length - 1]);
const firstIncomingId = getEntityId(incoming[0]);
if (lastPrevId && firstIncomingId && lastPrevId !== firstIncomingId) {
const prevIdSet = new Set(
previous.map(item => getEntityId(item)).filter(Boolean) as string[]
);
if (!prevIdSet.has(firstIncomingId)) return [...previous, ...incoming];
}
const merged = [...previous];
const indexById = new Map<string, number>();
for (let i = 0; i < merged.length; i += 1) {
const id = getEntityId(merged[i]);
if (id) indexById.set(id, i);
}
for (const post of incoming) {
const id = getEntityId(post);
if (!id) { merged.push(post); continue; }
const existingIndex = indexById.get(id);
if (existingIndex === undefined) {
indexById.set(id, merged.length);
merged.push(post);
} else {
merged[existingIndex] = post;
}
}
return merged;
}
export function mergeRefreshWindow(
previousInput: Post[] | null | undefined,
latestInput: Post[] | null | undefined
): Post[] {
const previous = Array.isArray(previousInput) ? previousInput : [];
const latest = Array.isArray(latestInput) ? latestInput : [];
if (latest.length === 0) return previous;
if (previous.length === 0) return latest;
const latestIdSet = new Set(
latest.map(item => getEntityId(item)).filter(Boolean) as string[]
);
const remaining = previous.filter(item => {
const id = getEntityId(item);
return !id || !latestIdSet.has(id);
});
return [...latest, ...remaining];
}

View File

@@ -1,16 +1,11 @@
/**
* 帖子服务
* 处理帖子的增删改查、点赞、收藏等功能
*
* @deprecated 此服务已弃用,请使用 ProcessPostUseCase 代替
* 互动操作(点赞、收藏)已委托给 processPostUseCase
* 帖子服务 - API 层薄封装
* 互动操作(点赞、收藏)请使用 postSyncService
*/
import { api, PaginatedData } from './api';
import { Post, CreatePostInput } from '../types';
import { CursorPaginationRequest, CursorPaginationResponse } from '../types/dto';
import { processPostUseCase } from '../core/usecases/ProcessPostUseCase';
import type { Post as PostEntity } from '../core/entities/Post';
// 帖子列表响应
interface PostListResponse {
@@ -129,55 +124,6 @@ class PostService {
}
}
// 点赞帖子
// @deprecated 请使用 processPostUseCase.likePost() 代替
async likePost(postId: string): Promise<Post | null> {
try {
const post = await processPostUseCase.likePost(postId);
// 转换为旧版 Post 类型以保持向后兼容
return post as unknown as Post;
} catch (error) {
console.error('[postService] likePost error:', error);
return null;
}
}
// 取消点赞帖子
// @deprecated 请使用 processPostUseCase.unlikePost() 代替
async unlikePost(postId: string): Promise<Post | null> {
try {
const post = await processPostUseCase.unlikePost(postId);
return post as unknown as Post;
} catch (error) {
console.error('[postService] unlikePost error:', error);
return null;
}
}
// 收藏帖子
// @deprecated 请使用 processPostUseCase.favoritePost() 代替
async favoritePost(postId: string): Promise<Post | null> {
try {
const post = await processPostUseCase.favoritePost(postId);
return post as unknown as Post;
} catch (error) {
console.error('[postService] favoritePost error:', error);
return null;
}
}
// 取消收藏帖子
// @deprecated 请使用 processPostUseCase.unfavoritePost() 代替
async unfavoritePost(postId: string): Promise<Post | null> {
try {
const post = await processPostUseCase.unfavoritePost(postId);
return post as unknown as Post;
} catch (error) {
console.error('[postService] unfavoritePost error:', error);
return null;
}
}
// 获取用户帖子列表
async getUserPosts(
userId: string,