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
This commit is contained in:
@@ -1,6 +1,24 @@
|
||||
/**
|
||||
* PostManager - 帖子管理核心模块
|
||||
*
|
||||
* 架构特点:
|
||||
* - 使用 CachedManager 基类,消除重复的缓存/去重逻辑
|
||||
* - 支持 Sources 模式进行分页加载
|
||||
*/
|
||||
|
||||
import { Post } from '../types';
|
||||
import { postService } from '../services/postService';
|
||||
import { CacheBus, CacheEvent } from './cacheBus';
|
||||
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;
|
||||
@@ -23,66 +41,57 @@ type PostManagerEvent =
|
||||
| CacheEvent<PostManagerSnapshot>
|
||||
| CacheEvent<{ error: unknown; context: string }>;
|
||||
|
||||
// ==================== 常量 ====================
|
||||
|
||||
const LIST_TTL = 30 * 1000;
|
||||
const DETAIL_TTL = 60 * 1000;
|
||||
|
||||
interface CacheEntry<T> {
|
||||
data: T;
|
||||
timestamp: number;
|
||||
ttl: number;
|
||||
}
|
||||
// ==================== Manager 实现 ====================
|
||||
|
||||
class PostManager extends CacheBus<PostManagerSnapshot, PostManagerEvent> {
|
||||
private listCache = new Map<string, CacheEntry<Post[]>>();
|
||||
class PostManager extends CachedManager<PostManagerSnapshot, PostManagerEvent> {
|
||||
/** 详情缓存 */
|
||||
private detailCache = new Map<string, CacheEntry<Post | null>>();
|
||||
private pendingRequests = new Map<string, Promise<any>>();
|
||||
|
||||
protected getSnapshot(): PostManagerSnapshot {
|
||||
return {
|
||||
listKeys: [...this.listCache.keys()],
|
||||
listKeys: [...this.memoryCache.keys()],
|
||||
detailKeys: [...this.detailCache.keys()],
|
||||
};
|
||||
}
|
||||
|
||||
private isExpired(entry: CacheEntry<any>): boolean {
|
||||
return Date.now() - entry.timestamp > entry.ttl;
|
||||
}
|
||||
|
||||
private async dedupe<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
|
||||
const pending = this.pendingRequests.get(key) as Promise<T> | undefined;
|
||||
if (pending) return pending;
|
||||
const request = fetcher().finally(() => {
|
||||
this.pendingRequests.delete(key);
|
||||
});
|
||||
this.pendingRequests.set(key, request);
|
||||
return request;
|
||||
}
|
||||
// ==================== 列表相关 ====================
|
||||
|
||||
private listKey(type: string, page: number, pageSize: number): string {
|
||||
return `${type}:${page}:${pageSize}`;
|
||||
return `list:${type}:${page}:${pageSize}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取帖子列表(传统分页模式)
|
||||
*/
|
||||
async getPosts(
|
||||
type = 'hot',
|
||||
type: PostListTab = 'hot',
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
pageSize = POST_LIST_PAGE_SIZE,
|
||||
forceRefresh = false
|
||||
): Promise<Post[]> {
|
||||
const key = this.listKey(type, page, pageSize);
|
||||
const cached = this.listCache.get(key);
|
||||
if (!forceRefresh && cached && !this.isExpired(cached)) {
|
||||
return cached.data;
|
||||
|
||||
// 检查有效缓存
|
||||
if (!forceRefresh && this.hasValidCache(key)) {
|
||||
return this.getFromCache<Post[]>(key)!;
|
||||
}
|
||||
|
||||
if (!forceRefresh && cached && this.isExpired(cached)) {
|
||||
// 过期缓存:后台刷新,立即返回旧数据
|
||||
if (!forceRefresh && this.hasExpiredCache(key)) {
|
||||
this.refreshPostsInBackground(key, type, page, pageSize);
|
||||
return cached.data;
|
||||
return this.getFromCache<Post[]>(key)!;
|
||||
}
|
||||
|
||||
return this.dedupe(`posts:list:${key}`, async () => {
|
||||
// 无缓存:发起请求
|
||||
return this.dedupe(`posts:${key}`, async () => {
|
||||
const response = await postService.getPosts(page, pageSize, type);
|
||||
const posts = response.list || [];
|
||||
this.listCache.set(key, { data: posts, timestamp: Date.now(), ttl: LIST_TTL });
|
||||
this.setCache(key, posts, LIST_TTL);
|
||||
this.notify({
|
||||
type: 'list_updated',
|
||||
payload: { key, posts },
|
||||
@@ -92,11 +101,16 @@ class PostManager extends CacheBus<PostManagerSnapshot, PostManagerEvent> {
|
||||
});
|
||||
}
|
||||
|
||||
private refreshPostsInBackground(key: string, type: string, page: number, pageSize: number): void {
|
||||
this.dedupe(`posts:list:bg:${key}`, async () => {
|
||||
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.listCache.set(key, { data: posts, timestamp: Date.now(), ttl: LIST_TTL });
|
||||
this.setCache(key, posts, LIST_TTL);
|
||||
this.notify({
|
||||
type: 'list_updated',
|
||||
payload: { key, posts },
|
||||
@@ -112,20 +126,39 @@ class PostManager extends CacheBus<PostManagerSnapshot, PostManagerEvent> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建帖子列表数据源(用于 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 && !this.isExpired(cached)) {
|
||||
|
||||
// 有效缓存
|
||||
if (!forceRefresh && cached && !isCacheExpired(cached)) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
if (!forceRefresh && cached && this.isExpired(cached)) {
|
||||
// 过期缓存:后台刷新
|
||||
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, { data: post, timestamp: Date.now(), ttl: DETAIL_TTL });
|
||||
this.detailCache.set(postId, createCacheEntry(post, DETAIL_TTL));
|
||||
this.notify({
|
||||
type: 'detail_updated',
|
||||
payload: { postId, post },
|
||||
@@ -138,7 +171,7 @@ class PostManager extends CacheBus<PostManagerSnapshot, PostManagerEvent> {
|
||||
private refreshPostDetailInBackground(postId: string): void {
|
||||
this.dedupe(`posts:detail:bg:${postId}`, async () => {
|
||||
const post = await postService.getPost(postId);
|
||||
this.detailCache.set(postId, { data: post, timestamp: Date.now(), ttl: DETAIL_TTL });
|
||||
this.detailCache.set(postId, createCacheEntry(post, DETAIL_TTL));
|
||||
this.notify({
|
||||
type: 'detail_updated',
|
||||
payload: { postId, post },
|
||||
@@ -154,21 +187,28 @@ class PostManager extends CacheBus<PostManagerSnapshot, PostManagerEvent> {
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 缓存管理 ====================
|
||||
|
||||
/**
|
||||
* 使缓存失效
|
||||
*/
|
||||
invalidate(postId?: string): void {
|
||||
if (postId) {
|
||||
this.detailCache.delete(postId);
|
||||
return;
|
||||
}
|
||||
this.listCache.clear();
|
||||
this.clearCache();
|
||||
this.detailCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有数据
|
||||
*/
|
||||
clear(): void {
|
||||
this.listCache.clear();
|
||||
this.clearCache();
|
||||
this.detailCache.clear();
|
||||
this.pendingRequests.clear();
|
||||
this.clearPendingRequests();
|
||||
}
|
||||
}
|
||||
|
||||
export const postManager = new PostManager();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user