/** * 群组列表数据源抽象:游标、偏移分页实现同一契约,GroupManager 只依赖接口。 */ import { GroupResponse, GroupMemberResponse, GroupListResponse, GroupMemberListResponse, CursorPaginationRequest, } from '../types/dto'; import { groupService } from '../services/groupService'; import { groupCacheRepository } from '@/database'; export const GROUP_LIST_PAGE_SIZE = 20; export const GROUP_MEMBER_LIST_PAGE_SIZE = 50; // ==================== 群组列表数据源 ==================== /** 单次拉取结果(一页或一批) */ export interface GroupListPage { items: GroupResponse[]; /** 在当前源上是否还能再 loadNext 一页 */ hasMore: boolean; } /** * 分页式群组列表源:restart 后开始新序列;首次 loadNext 为第一页,之后为后续页。 */ export interface IGroupListPagedSource { restart(): void; loadNext(): Promise; /** 至少成功 loadNext 过一次且仍有下一页时为 true */ readonly hasMore: boolean; } /** * 远端群组列表(游标分页) */ export class NetworkCursorGroupListPagedSource implements IGroupListPagedSource { private nextCursor: string | null = null; private hasMoreAfterLastLoad = false; private loadedOnce = false; private readonly pageSize: number; constructor(pageSize: number = GROUP_LIST_PAGE_SIZE) { this.pageSize = pageSize; } restart(): void { this.nextCursor = null; this.hasMoreAfterLastLoad = false; this.loadedOnce = false; } get hasMore(): boolean { return this.loadedOnce && this.hasMoreAfterLastLoad; } async loadNext(): Promise { const params: CursorPaginationRequest = { cursor: this.nextCursor ?? '', page_size: this.pageSize, }; const result = await groupService.getGroupsCursor(params); this.hasMoreAfterLastLoad = result.has_more ?? false; this.nextCursor = result.next_cursor ?? null; this.loadedOnce = true; return { items: result.list ?? [], hasMore: this.hasMoreAfterLastLoad }; } } /** * 远端群组列表(偏移分页) */ export class NetworkOffsetGroupListPagedSource implements IGroupListPagedSource { private nextPage = 1; private hasMoreAfterLastLoad = false; private loadedOnce = false; private readonly pageSize: number; constructor(pageSize: number = GROUP_LIST_PAGE_SIZE) { this.pageSize = pageSize; } restart(): void { this.nextPage = 1; this.hasMoreAfterLastLoad = false; this.loadedOnce = false; } get hasMore(): boolean { return this.loadedOnce && this.hasMoreAfterLastLoad; } async loadNext(): Promise { const result = await groupService.getGroups(this.nextPage, this.pageSize); const items = result.list ?? []; const totalPages = result.total_pages ?? 1; this.hasMoreAfterLastLoad = this.nextPage < totalPages; this.nextPage++; this.loadedOnce = true; return { items, hasMore: this.hasMoreAfterLastLoad }; } } /** * SQLite 群组列表缓存(单批,无后续页) */ export class SqliteGroupListPagedSource implements IGroupListPagedSource { private consumed = false; restart(): void { this.consumed = false; } get hasMore(): boolean { return false; } async loadNext(): Promise { if (this.consumed) { return { items: [], hasMore: false }; } this.consumed = true; try { const items = await groupCacheRepository.getAll(); return { items, hasMore: false }; } catch (e) { console.warn('[SqliteGroupListPagedSource] 读取群组列表缓存失败:', e); return { items: [], hasMore: false }; } } } // ==================== 群组成员列表数据源 ==================== /** 成员列表单次拉取结果 */ export interface GroupMemberListPage { items: GroupMemberResponse[]; hasMore: boolean; } /** * 分页式群组成员列表源 */ export interface IGroupMemberListPagedSource { restart(): void; loadNext(): Promise; readonly hasMore: boolean; } /** * 远端群组成员列表(游标分页) */ export class NetworkCursorGroupMemberListPagedSource implements IGroupMemberListPagedSource { private nextCursor: string | null = null; private hasMoreAfterLastLoad = false; private loadedOnce = false; private readonly pageSize: number; private readonly groupId: string; constructor(groupId: string, pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE) { this.groupId = groupId; this.pageSize = pageSize; } restart(): void { this.nextCursor = null; this.hasMoreAfterLastLoad = false; this.loadedOnce = false; } get hasMore(): boolean { return this.loadedOnce && this.hasMoreAfterLastLoad; } async loadNext(): Promise { const params: CursorPaginationRequest = { cursor: this.nextCursor ?? '', page_size: this.pageSize, }; const result = await groupService.getGroupMembersCursor(this.groupId, params); this.hasMoreAfterLastLoad = result.has_more ?? false; this.nextCursor = result.next_cursor ?? null; this.loadedOnce = true; return { items: result.list ?? [], hasMore: this.hasMoreAfterLastLoad }; } } /** * 远端群组成员列表(偏移分页) */ export class NetworkOffsetGroupMemberListPagedSource implements IGroupMemberListPagedSource { private nextPage = 1; private hasMoreAfterLastLoad = false; private loadedOnce = false; private readonly pageSize: number; private readonly groupId: string; constructor(groupId: string, pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE) { this.groupId = groupId; this.pageSize = pageSize; } restart(): void { this.nextPage = 1; this.hasMoreAfterLastLoad = false; this.loadedOnce = false; } get hasMore(): boolean { return this.loadedOnce && this.hasMoreAfterLastLoad; } async loadNext(): Promise { const result = await groupService.getMembers( this.groupId, this.nextPage, this.pageSize ); const items = result.list ?? []; const totalPages = result.total_pages ?? 1; this.hasMoreAfterLastLoad = this.nextPage < totalPages; this.nextPage++; this.loadedOnce = true; return { items, hasMore: this.hasMoreAfterLastLoad }; } } /** * SQLite 群组成员列表缓存(单批,无后续页) */ export class SqliteGroupMemberListPagedSource implements IGroupMemberListPagedSource { private consumed = false; private readonly groupId: string; constructor(groupId: string) { this.groupId = groupId; } restart(): void { this.consumed = false; } get hasMore(): boolean { return false; } async loadNext(): Promise { if (this.consumed) { return { items: [], hasMore: false }; } this.consumed = true; try { const items = await groupCacheRepository.getMembers(this.groupId); return { items, hasMore: false }; } catch (e) { console.warn('[SqliteGroupMemberListPagedSource] 读取成员列表缓存失败:', e); return { items: [], hasMore: false }; } } } // ==================== 工厂函数 ==================== /** 远端群组列表分页策略 */ export type RemoteGroupListSourceKind = 'cursor' | 'offset'; export function createRemoteGroupListSource( kind: RemoteGroupListSourceKind = 'cursor', pageSize: number = GROUP_LIST_PAGE_SIZE ): IGroupListPagedSource { if (kind === 'offset') { return new NetworkOffsetGroupListPagedSource(pageSize); } return new NetworkCursorGroupListPagedSource(pageSize); } export function createRemoteGroupMemberListSource( groupId: string, kind: RemoteGroupListSourceKind = 'cursor', pageSize: number = GROUP_MEMBER_LIST_PAGE_SIZE ): IGroupMemberListPagedSource { if (kind === 'offset') { return new NetworkOffsetGroupMemberListPagedSource(groupId, pageSize); } return new NetworkCursorGroupMemberListPagedSource(groupId, pageSize); }