refactor(stores): unify data sources pattern and extract BaseManager base class
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

- 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:
2026-03-26 16:46:22 +08:00
parent b6583e07c8
commit 4b89b50006
13 changed files with 858 additions and 628 deletions

View File

@@ -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();