Files
frontend/src/stores/postManager.ts
lan 4b89b50006
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 3m4s
Frontend CI / ota-android (push) Successful in 11m9s
Frontend CI / build-android-apk (push) Successful in 1h23m57s
refactor(stores): unify data sources pattern and extract BaseManager base class
- Add BaseManager/CachedManager base classes to eliminate duplicate cache logic
- Add postListSources.ts with IPostListPagedSource interface
- Add groupListSources.ts with IGroupListPagedSource interface
- Refactor postManager to use CachedManager and Sources pattern
- Refactor groupManager to use CachedManager and Sources pattern
- Clean up duplicate methods in groupService.ts
- Fix TypeScript errors and remove mock data
2026-03-26 16:46:22 +08:00

215 lines
5.6 KiB
TypeScript

/**
* PostManager - 帖子管理核心模块
*
* 架构特点:
* - 使用 CachedManager 基类,消除重复的缓存/去重逻辑
* - 支持 Sources 模式进行分页加载
*/
import { Post } from '../types';
import { postService } from '../services/postService';
import { CacheEvent } from './cacheBus';
import { CachedManager, CacheEntry, createCacheEntry, isCacheExpired } from './baseManager';
import {
IPostListPagedSource,
PostListTab,
createRemotePostListSource,
RemotePostListSourceKind,
POST_LIST_PAGE_SIZE,
} from './postListSources';
// ==================== 类型定义 ====================
interface PostListPayload {
key: string;
posts: Post[];
}
interface PostDetailPayload {
postId: string;
post: Post | null;
}
interface PostManagerSnapshot {
listKeys: string[];
detailKeys: string[];
}
type PostManagerEvent =
| CacheEvent<PostListPayload>
| CacheEvent<PostDetailPayload>
| CacheEvent<PostManagerSnapshot>
| CacheEvent<{ error: unknown; context: string }>;
// ==================== 常量 ====================
const LIST_TTL = 30 * 1000;
const DETAIL_TTL = 60 * 1000;
// ==================== Manager 实现 ====================
class PostManager extends CachedManager<PostManagerSnapshot, PostManagerEvent> {
/** 详情缓存 */
private detailCache = new Map<string, CacheEntry<Post | null>>();
protected getSnapshot(): PostManagerSnapshot {
return {
listKeys: [...this.memoryCache.keys()],
detailKeys: [...this.detailCache.keys()],
};
}
// ==================== 列表相关 ====================
private listKey(type: string, page: number, pageSize: number): string {
return `list:${type}:${page}:${pageSize}`;
}
/**
* 获取帖子列表(传统分页模式)
*/
async getPosts(
type: PostListTab = 'hot',
page = 1,
pageSize = POST_LIST_PAGE_SIZE,
forceRefresh = false
): Promise<Post[]> {
const key = this.listKey(type, page, pageSize);
// 检查有效缓存
if (!forceRefresh && this.hasValidCache(key)) {
return this.getFromCache<Post[]>(key)!;
}
// 过期缓存:后台刷新,立即返回旧数据
if (!forceRefresh && this.hasExpiredCache(key)) {
this.refreshPostsInBackground(key, type, page, pageSize);
return this.getFromCache<Post[]>(key)!;
}
// 无缓存:发起请求
return this.dedupe(`posts:${key}`, async () => {
const response = await postService.getPosts(page, pageSize, type);
const posts = response.list || [];
this.setCache(key, posts, LIST_TTL);
this.notify({
type: 'list_updated',
payload: { key, posts },
timestamp: Date.now(),
});
return posts;
});
}
private refreshPostsInBackground(
key: string,
type: PostListTab,
page: number,
pageSize: number
): void {
this.dedupe(`posts:bg:${key}`, async () => {
const response = await postService.getPosts(page, pageSize, type);
const posts = response.list || [];
this.setCache(key, posts, LIST_TTL);
this.notify({
type: 'list_updated',
payload: { key, posts },
timestamp: Date.now(),
});
return posts;
}).catch((error) => {
this.notify({
type: 'error',
payload: { error, context: 'refreshPostsInBackground' },
timestamp: Date.now(),
});
});
}
/**
* 创建帖子列表数据源(用于 Sources 模式)
*/
createPostListSource(
options: { tab?: PostListTab; channelId?: string; pageSize?: number } = {},
kind: RemotePostListSourceKind = 'cursor'
): IPostListPagedSource {
return createRemotePostListSource(kind, options);
}
// ==================== 详情相关 ====================
/**
* 获取帖子详情
*/
async getPostDetail(postId: string, forceRefresh = false): Promise<Post | null> {
const cached = this.detailCache.get(postId);
// 有效缓存
if (!forceRefresh && cached && !isCacheExpired(cached)) {
return cached.data;
}
// 过期缓存:后台刷新
if (!forceRefresh && cached && isCacheExpired(cached) && cached.data) {
this.refreshPostDetailInBackground(postId);
return cached.data;
}
// 无缓存:发起请求
return this.dedupe(`posts:detail:${postId}`, async () => {
const post = await postService.getPost(postId);
this.detailCache.set(postId, createCacheEntry(post, DETAIL_TTL));
this.notify({
type: 'detail_updated',
payload: { postId, post },
timestamp: Date.now(),
});
return post;
});
}
private refreshPostDetailInBackground(postId: string): void {
this.dedupe(`posts:detail:bg:${postId}`, async () => {
const post = await postService.getPost(postId);
this.detailCache.set(postId, createCacheEntry(post, DETAIL_TTL));
this.notify({
type: 'detail_updated',
payload: { postId, post },
timestamp: Date.now(),
});
return post;
}).catch((error) => {
this.notify({
type: 'error',
payload: { error, context: 'refreshPostDetailInBackground' },
timestamp: Date.now(),
});
});
}
// ==================== 缓存管理 ====================
/**
* 使缓存失效
*/
invalidate(postId?: string): void {
if (postId) {
this.detailCache.delete(postId);
return;
}
this.clearCache();
this.detailCache.clear();
}
/**
* 清除所有数据
*/
clear(): void {
this.clearCache();
this.detailCache.clear();
this.clearPendingRequests();
}
}
export const postManager = new PostManager();