Files
frontend/src/stores/user/UserManager.ts

141 lines
4.7 KiB
TypeScript
Raw Normal View History

/**
* UserManager -
*
* 使 Zustand store
* API
*/
import type { UserDTO } from '../../types/dto';
2026-04-13 01:30:37 +08:00
import { authService } from '@/services/auth';
import { userCacheRepository } from '@/database';
import { useUserManagerStore } from './userStore';
import { createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
import { createDedupe } from '../utils/requestDedupe';
const dedupe = createDedupe(() => useUserManagerStore);
// 将 fetchCurrentUserFromAPI 的判别结果转为 User | null。
// 网络故障/认证失败时返回 null由上层缓存兜底避免空数据覆盖缓存
// 注意UserManager 只负责缓存,不触发登出,登出由 authStore 统一处理。
async function fetchCurrentUserOrNull(): Promise<UserDTO | null> {
const result = await authService.fetchCurrentUserFromAPI();
return result.kind === 'user' ? result.user : null;
}
// ==================== UserManager 类 ====================
class UserManager {
async getCurrentUser(forceRefresh = false): Promise<UserDTO | null> {
const store = useUserManagerStore.getState();
const entry = store.currentUserEntry;
if (!forceRefresh && entry && !isCacheExpired(entry)) {
return entry.data;
}
if (!forceRefresh && entry && isCacheExpired(entry)) {
this.refreshCurrentUserInBackground();
return entry.data;
}
if (!forceRefresh) {
const cachedFromDb = await userCacheRepository.getCurrent();
if (cachedFromDb) {
const newEntry = createCacheEntry(cachedFromDb, DEFAULT_TTL.USER);
store.setCurrentUserEntry(newEntry);
this.refreshCurrentUserInBackground();
return cachedFromDb;
}
}
return dedupe('users:current', async () => {
const user = await fetchCurrentUserOrNull();
// 仅在拿到真实用户时刷新缓存;网络/认证失败时保留旧缓存,避免误清空
if (user) {
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
store.setCurrentUserEntry(newEntry);
userCacheRepository.saveCurrent(user).catch(() => {});
userCacheRepository.save(user).catch(() => {});
}
return user;
});
}
private refreshCurrentUserInBackground(): void {
dedupe('users:current:bg', async () => {
const store = useUserManagerStore.getState();
const user = await fetchCurrentUserOrNull();
if (user) {
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
store.setCurrentUserEntry(newEntry);
userCacheRepository.saveCurrent(user).catch(() => {});
userCacheRepository.save(user).catch(() => {});
}
return user;
}).catch((error) => {
console.error('[UserManager] refreshCurrentUserInBackground error:', error);
});
}
async getUserById(userId: string, forceRefresh = false): Promise<UserDTO | null> {
const store = useUserManagerStore.getState();
const entry = store.usersMap.get(userId);
if (!forceRefresh && entry && !isCacheExpired(entry)) {
return entry.data;
}
if (!forceRefresh && entry && isCacheExpired(entry)) {
this.refreshUserByIdInBackground(userId);
return entry.data;
}
if (!forceRefresh) {
const cachedFromDb = await userCacheRepository.get(userId);
if (cachedFromDb) {
const newEntry = createCacheEntry(cachedFromDb, DEFAULT_TTL.USER);
store.setUser(userId, cachedFromDb);
this.refreshUserByIdInBackground(userId);
return cachedFromDb;
}
}
return dedupe(`users:detail:${userId}`, async () => {
const store = useUserManagerStore.getState();
const user = await authService.fetchUserByIdFromAPI(userId);
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
store.setUser(userId, user);
if (user) {
userCacheRepository.save(user).catch(() => {});
}
return user;
});
}
private refreshUserByIdInBackground(userId: string): void {
dedupe(`users:detail:bg:${userId}`, async () => {
const store = useUserManagerStore.getState();
const user = await authService.fetchUserByIdFromAPI(userId);
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
store.setUser(userId, user);
if (user) {
userCacheRepository.save(user).catch(() => {});
}
return user;
}).catch((error) => {
console.error('[UserManager] refreshUserByIdInBackground error:', error);
});
}
invalidateUsers(): void {
useUserManagerStore.getState().invalidateAll();
}
}
export const userManager = new UserManager();
export { UserManager };
export default userManager;