- 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
215 lines
5.6 KiB
TypeScript
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();
|