287 lines
8.4 KiB
TypeScript
287 lines
8.4 KiB
TypeScript
|
|
/**
|
||
|
|
* 群组状态 Zustand Store
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { create } from 'zustand';
|
||
|
|
import type {
|
||
|
|
GroupResponse,
|
||
|
|
GroupMemberResponse,
|
||
|
|
} from '../../types/dto';
|
||
|
|
import { CacheEntry, createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
|
||
|
|
|
||
|
|
// ==================== 状态接口 ====================
|
||
|
|
|
||
|
|
export interface GroupManagerState {
|
||
|
|
// 群组列表缓存
|
||
|
|
groupsListEntry: CacheEntry<GroupResponse[]> | null;
|
||
|
|
|
||
|
|
// 群组详情缓存
|
||
|
|
groupsMap: Map<string, CacheEntry<GroupResponse>>;
|
||
|
|
|
||
|
|
// 群组成员缓存 (key: members:groupId:page:pageSize)
|
||
|
|
membersMap: Map<string, CacheEntry<GroupMemberResponse[]>>;
|
||
|
|
|
||
|
|
// 加载状态
|
||
|
|
isLoadingGroups: boolean;
|
||
|
|
loadingGroupIds: Set<string>;
|
||
|
|
loadingMemberKeys: Set<string>;
|
||
|
|
|
||
|
|
// 待处理请求(去重)
|
||
|
|
pendingRequests: Map<string, Promise<any>>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface GroupManagerActions {
|
||
|
|
// 获取状态
|
||
|
|
getGroupsList: () => GroupResponse[] | null;
|
||
|
|
getGroup: (groupId: string) => GroupResponse | null;
|
||
|
|
getMembers: (groupId: string, page?: number, pageSize?: number) => GroupMemberResponse[] | null;
|
||
|
|
isGroupsListExpired: () => boolean;
|
||
|
|
isGroupExpired: (groupId: string) => boolean;
|
||
|
|
isMembersExpired: (groupId: string, page?: number, pageSize?: number) => boolean;
|
||
|
|
isLoadingGroup: (groupId: string) => boolean;
|
||
|
|
isLoadingMembers: (groupId: string, page?: number, pageSize?: number) => boolean;
|
||
|
|
|
||
|
|
// 设置状态
|
||
|
|
setGroupsList: (groups: GroupResponse[]) => void;
|
||
|
|
setGroupsListEntry: (entry: CacheEntry<GroupResponse[]>) => void;
|
||
|
|
setGroup: (groupId: string, group: GroupResponse) => void;
|
||
|
|
setMembers: (groupId: string, members: GroupMemberResponse[], page?: number, pageSize?: number) => void;
|
||
|
|
setLoadingGroups: (loading: boolean) => void;
|
||
|
|
setLoadingGroup: (groupId: string, loading: boolean) => void;
|
||
|
|
setLoadingMembers: (groupId: string, loading: boolean, page?: number, pageSize?: number) => void;
|
||
|
|
|
||
|
|
// 缓存管理
|
||
|
|
invalidateGroupsList: () => void;
|
||
|
|
invalidateGroup: (groupId: string) => void;
|
||
|
|
invalidateMembers: (groupId: string) => void;
|
||
|
|
invalidateAll: () => void;
|
||
|
|
|
||
|
|
// 请求去重
|
||
|
|
getPendingRequest: <T>(key: string) => Promise<T> | undefined;
|
||
|
|
setPendingRequest: <T>(key: string, request: Promise<T>) => void;
|
||
|
|
deletePendingRequest: (key: string) => void;
|
||
|
|
clearPendingRequests: () => void;
|
||
|
|
|
||
|
|
// 重置
|
||
|
|
reset: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==================== 辅助函数 ====================
|
||
|
|
|
||
|
|
function buildMembersKey(groupId: string, page: number = 1, pageSize: number = 20): string {
|
||
|
|
return `members:${groupId}:${page}:${pageSize}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ==================== 初始状态 ====================
|
||
|
|
|
||
|
|
const initialState: GroupManagerState = {
|
||
|
|
groupsListEntry: null,
|
||
|
|
groupsMap: new Map(),
|
||
|
|
membersMap: new Map(),
|
||
|
|
isLoadingGroups: false,
|
||
|
|
loadingGroupIds: new Set(),
|
||
|
|
loadingMemberKeys: new Set(),
|
||
|
|
pendingRequests: new Map(),
|
||
|
|
};
|
||
|
|
|
||
|
|
// ==================== Store 创建 ====================
|
||
|
|
|
||
|
|
export type GroupManagerStore = GroupManagerState & GroupManagerActions;
|
||
|
|
|
||
|
|
export const useGroupManagerStore = create<GroupManagerStore>((set, get) => ({
|
||
|
|
// ==================== 初始状态 ====================
|
||
|
|
...initialState,
|
||
|
|
|
||
|
|
// ==================== 获取状态 ====================
|
||
|
|
|
||
|
|
getGroupsList: () => {
|
||
|
|
const entry = get().groupsListEntry;
|
||
|
|
if (!entry) return null;
|
||
|
|
if (isCacheExpired(entry)) return null;
|
||
|
|
return entry.data;
|
||
|
|
},
|
||
|
|
|
||
|
|
getGroup: (groupId: string) => {
|
||
|
|
const entry = get().groupsMap.get(groupId);
|
||
|
|
if (!entry) return null;
|
||
|
|
if (isCacheExpired(entry)) return null;
|
||
|
|
return entry.data;
|
||
|
|
},
|
||
|
|
|
||
|
|
getMembers: (groupId: string, page = 1, pageSize = 20) => {
|
||
|
|
const key = buildMembersKey(groupId, page, pageSize);
|
||
|
|
const entry = get().membersMap.get(key);
|
||
|
|
if (!entry) return null;
|
||
|
|
if (isCacheExpired(entry)) return null;
|
||
|
|
return entry.data;
|
||
|
|
},
|
||
|
|
|
||
|
|
isGroupsListExpired: () => {
|
||
|
|
const entry = get().groupsListEntry;
|
||
|
|
if (!entry) return true;
|
||
|
|
return isCacheExpired(entry);
|
||
|
|
},
|
||
|
|
|
||
|
|
isGroupExpired: (groupId: string) => {
|
||
|
|
const entry = get().groupsMap.get(groupId);
|
||
|
|
if (!entry) return true;
|
||
|
|
return isCacheExpired(entry);
|
||
|
|
},
|
||
|
|
|
||
|
|
isMembersExpired: (groupId: string, page = 1, pageSize = 20) => {
|
||
|
|
const key = buildMembersKey(groupId, page, pageSize);
|
||
|
|
const entry = get().membersMap.get(key);
|
||
|
|
if (!entry) return true;
|
||
|
|
return isCacheExpired(entry);
|
||
|
|
},
|
||
|
|
|
||
|
|
isLoadingGroup: (groupId: string) => {
|
||
|
|
return get().loadingGroupIds.has(groupId);
|
||
|
|
},
|
||
|
|
|
||
|
|
isLoadingMembers: (groupId: string, page = 1, pageSize = 20) => {
|
||
|
|
const key = buildMembersKey(groupId, page, pageSize);
|
||
|
|
return get().loadingMemberKeys.has(key);
|
||
|
|
},
|
||
|
|
|
||
|
|
// ==================== 设置状态 ====================
|
||
|
|
|
||
|
|
setGroupsList: (groups: GroupResponse[]) => {
|
||
|
|
set({
|
||
|
|
groupsListEntry: createCacheEntry(groups, DEFAULT_TTL.GROUP),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
setGroupsListEntry: (entry: CacheEntry<GroupResponse[]>) => {
|
||
|
|
set({ groupsListEntry: entry });
|
||
|
|
},
|
||
|
|
|
||
|
|
setGroup: (groupId: string, group: GroupResponse) => {
|
||
|
|
set(state => {
|
||
|
|
const newGroupsMap = new Map(state.groupsMap);
|
||
|
|
newGroupsMap.set(groupId, createCacheEntry(group, DEFAULT_TTL.GROUP));
|
||
|
|
return { groupsMap: newGroupsMap };
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
setMembers: (groupId: string, members: GroupMemberResponse[], page = 1, pageSize = 20) => {
|
||
|
|
const key = buildMembersKey(groupId, page, pageSize);
|
||
|
|
set(state => {
|
||
|
|
const newMembersMap = new Map(state.membersMap);
|
||
|
|
newMembersMap.set(key, createCacheEntry(members, DEFAULT_TTL.GROUP_MEMBERS));
|
||
|
|
return { membersMap: newMembersMap };
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
setLoadingGroups: (loading: boolean) => {
|
||
|
|
set({ isLoadingGroups: loading });
|
||
|
|
},
|
||
|
|
|
||
|
|
setLoadingGroup: (groupId: string, loading: boolean) => {
|
||
|
|
set(state => {
|
||
|
|
const newLoadingGroupIds = new Set(state.loadingGroupIds);
|
||
|
|
if (loading) {
|
||
|
|
newLoadingGroupIds.add(groupId);
|
||
|
|
} else {
|
||
|
|
newLoadingGroupIds.delete(groupId);
|
||
|
|
}
|
||
|
|
return { loadingGroupIds: newLoadingGroupIds };
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
setLoadingMembers: (groupId: string, loading: boolean, page = 1, pageSize = 20) => {
|
||
|
|
const key = buildMembersKey(groupId, page, pageSize);
|
||
|
|
set(state => {
|
||
|
|
const newLoadingMemberKeys = new Set(state.loadingMemberKeys);
|
||
|
|
if (loading) {
|
||
|
|
newLoadingMemberKeys.add(key);
|
||
|
|
} else {
|
||
|
|
newLoadingMemberKeys.delete(key);
|
||
|
|
}
|
||
|
|
return { loadingMemberKeys: newLoadingMemberKeys };
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
// ==================== 缓存管理 ====================
|
||
|
|
|
||
|
|
invalidateGroupsList: () => {
|
||
|
|
set({ groupsListEntry: null });
|
||
|
|
},
|
||
|
|
|
||
|
|
invalidateGroup: (groupId: string) => {
|
||
|
|
set(state => {
|
||
|
|
const newGroupsMap = new Map(state.groupsMap);
|
||
|
|
newGroupsMap.delete(groupId);
|
||
|
|
|
||
|
|
// 删除相关的成员缓存
|
||
|
|
const newMembersMap = new Map(state.membersMap);
|
||
|
|
for (const key of newMembersMap.keys()) {
|
||
|
|
if (key.startsWith(`members:${groupId}:`)) {
|
||
|
|
newMembersMap.delete(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { groupsMap: newGroupsMap, membersMap: newMembersMap };
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
invalidateMembers: (groupId: string) => {
|
||
|
|
set(state => {
|
||
|
|
const newMembersMap = new Map(state.membersMap);
|
||
|
|
for (const key of newMembersMap.keys()) {
|
||
|
|
if (key.startsWith(`members:${groupId}:`)) {
|
||
|
|
newMembersMap.delete(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return { membersMap: newMembersMap };
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
invalidateAll: () => {
|
||
|
|
set({
|
||
|
|
groupsListEntry: null,
|
||
|
|
groupsMap: new Map(),
|
||
|
|
membersMap: new Map(),
|
||
|
|
pendingRequests: new Map(),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
// ==================== 请求去重 ====================
|
||
|
|
|
||
|
|
getPendingRequest: <T>(key: string) => {
|
||
|
|
return get().pendingRequests.get(key) as Promise<T> | undefined;
|
||
|
|
},
|
||
|
|
|
||
|
|
setPendingRequest: <T>(key: string, request: Promise<T>) => {
|
||
|
|
set(state => {
|
||
|
|
const newPendingRequests = new Map(state.pendingRequests);
|
||
|
|
newPendingRequests.set(key, request);
|
||
|
|
return { pendingRequests: newPendingRequests };
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
deletePendingRequest: (key: string) => {
|
||
|
|
set(state => {
|
||
|
|
const newPendingRequests = new Map(state.pendingRequests);
|
||
|
|
newPendingRequests.delete(key);
|
||
|
|
return { pendingRequests: newPendingRequests };
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
clearPendingRequests: () => {
|
||
|
|
set({ pendingRequests: new Map() });
|
||
|
|
},
|
||
|
|
|
||
|
|
// ==================== 重置 ====================
|
||
|
|
|
||
|
|
reset: () => {
|
||
|
|
set({
|
||
|
|
...initialState,
|
||
|
|
groupsMap: new Map(),
|
||
|
|
membersMap: new Map(),
|
||
|
|
loadingGroupIds: new Set(),
|
||
|
|
loadingMemberKeys: new Set(),
|
||
|
|
pendingRequests: new Map(),
|
||
|
|
});
|
||
|
|
},
|
||
|
|
}));
|