refactor(database): migrate to new modular database layer and unify data access
- Remove legacy database.ts, LocalDataSource.ts, and MessageRepository.ts - Create new src/database/ module with messageRepository, userCacheRepository, conversationRepository, and groupCacheRepository - Update all consumers to import from @/database instead of services/database - Add web platform blur handling for modal components to fix focus issues - Flatten SystemMessageItem and NotificationsScreen styles for consistent design - Add draggable slider in ChatSettingsScreen and dynamic font size support - Introduce 9 new chat color themes - Add profile screens for about, terms, and privacy policy with navigation routes - Add policy links to login and registration screens - Fix post share URL format from /posts/ to /post/
This commit is contained in:
@@ -20,10 +20,10 @@ import { useSessionStore } from './sessionStore';
|
||||
import {
|
||||
initDatabase,
|
||||
closeDatabase,
|
||||
saveUserCache,
|
||||
saveCurrentUserCache,
|
||||
clearCurrentUserCache,
|
||||
} from '../services/database';
|
||||
isDbInitialized,
|
||||
getCurrentDbUserId,
|
||||
} from '@/database';
|
||||
import { userCacheRepository } from '@/database';
|
||||
|
||||
// AsyncStorage 中保存已登录用户 ID 的 key
|
||||
const USER_ID_KEY = 'auth_user_id';
|
||||
@@ -54,8 +54,8 @@ async function clearUserId(): Promise<void> {
|
||||
// ── 写用户缓存(DB 已就绪后调用)──
|
||||
async function cacheUser(user: User): Promise<void> {
|
||||
try {
|
||||
await saveUserCache(user);
|
||||
await saveCurrentUserCache(user);
|
||||
await userCacheRepository.save(user);
|
||||
await userCacheRepository.saveCurrent(user);
|
||||
} catch (err) {
|
||||
console.warn('[AuthStore] 写用户缓存失败:', err);
|
||||
}
|
||||
@@ -225,7 +225,7 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
// 3. 重置通话状态(清理所有事件订阅和定时器)
|
||||
callStore.getState().reset();
|
||||
// 4. 清除 DB 中的用户缓存(DB 此时一定已初始化)
|
||||
await clearCurrentUserCache().catch(() => {});
|
||||
await userCacheRepository.clearCurrent().catch(() => {});
|
||||
// 5. 关闭数据库连接
|
||||
await closeDatabase();
|
||||
// 6. 清除持久化的 userId
|
||||
@@ -262,7 +262,7 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
try {
|
||||
await initDatabase(savedUserId);
|
||||
} catch (dbErr) {
|
||||
console.warn('[AuthStore] DB 预初始化失败,将尝试在 API 成功后重新初始化:', dbErr);
|
||||
console.warn('[AuthStore] DB 预初始化失败,将在 API 成功后重试:', dbErr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,16 +272,21 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
if (user) {
|
||||
const userId = String(user.id);
|
||||
|
||||
// 4. 若 userId 发生变化(极少数情况),重新初始化 DB 并更新持久化
|
||||
if (!savedUserId || savedUserId !== userId) {
|
||||
// 4. 确保 DB 已初始化(如果预初始化失败,这里会重试)
|
||||
const dbReady = isDbInitialized() && getCurrentDbUserId() === userId;
|
||||
if (!dbReady) {
|
||||
await initDatabase(userId);
|
||||
}
|
||||
|
||||
// 5. 更新持久化的 userId
|
||||
if (!savedUserId || savedUserId !== userId) {
|
||||
await saveUserId(userId);
|
||||
}
|
||||
|
||||
// 5. DB 已就绪,更新用户缓存
|
||||
// 6. DB 已就绪,更新用户缓存
|
||||
await cacheUser(user);
|
||||
|
||||
// 6. 同步 userId 到 sessionStore
|
||||
// 7. 同步 userId 到 sessionStore
|
||||
useSessionStore.getState().setUserId(userId);
|
||||
|
||||
set({
|
||||
@@ -290,7 +295,7 @@ export const useAuthStore = create<AuthState>((set) => ({
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
// 7. 启动 SSE
|
||||
// 8. 启动 SSE
|
||||
await startRealtime();
|
||||
} else {
|
||||
// Token 已失效或不存在
|
||||
|
||||
@@ -81,6 +81,132 @@ export const CHAT_THEMES = [
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'coral',
|
||||
name: '珊瑚橙',
|
||||
primary: '#FF6B6B',
|
||||
secondary: '#FFF0F0',
|
||||
bubble: '#FFD4D4',
|
||||
icon: '🐠',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#FFF5F5',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'rose',
|
||||
name: '玫瑰粉',
|
||||
primary: '#E91E63',
|
||||
secondary: '#FCE4EC',
|
||||
bubble: '#F8BBD9',
|
||||
icon: '🌹',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#FCE4EC',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'indigo',
|
||||
name: '靛蓝紫',
|
||||
primary: '#3F51B5',
|
||||
secondary: '#E8EAF6',
|
||||
bubble: '#C5CAE9',
|
||||
icon: '🐉',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#E8EAF6',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'mint',
|
||||
name: '薄荷绿',
|
||||
primary: '#00BCD4',
|
||||
secondary: '#E0F7FA',
|
||||
bubble: '#B2EBF2',
|
||||
icon: '🧊',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#E0F7FA',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'cherry',
|
||||
name: '樱桃红',
|
||||
primary: '#D32F2F',
|
||||
secondary: '#FFEBEE',
|
||||
bubble: '#FFCDD2',
|
||||
icon: '🍒',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#FFEBEE',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'lavender',
|
||||
name: '薰衣草',
|
||||
primary: '#7E57C2',
|
||||
secondary: '#EDE7F6',
|
||||
bubble: '#D1C4E9',
|
||||
icon: '💜',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#EDE7F6',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'sunset',
|
||||
name: '落日橙',
|
||||
primary: '#FF5722',
|
||||
secondary: '#FBE9E7',
|
||||
bubble: '#FFCCBC',
|
||||
icon: '🌅',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#FBE9E7',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'ocean',
|
||||
name: '深海蓝',
|
||||
primary: '#006064',
|
||||
secondary: '#B2EBF2',
|
||||
bubble: '#80DEEA',
|
||||
icon: '🌊',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#E0F7FA',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
{
|
||||
id: 'forest',
|
||||
name: '森林绿',
|
||||
primary: '#1B5E20',
|
||||
secondary: '#C8E6C9',
|
||||
bubble: '#A5D6A7',
|
||||
icon: '🌳',
|
||||
card: '#FFFFFF',
|
||||
inputBg: '#FFFFFF',
|
||||
panelBg: '#E8F5E9',
|
||||
headerBg: '#FFFFFF',
|
||||
textPrimary: '#1A1A1A',
|
||||
textSecondary: '#666666',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type ChatThemeId = typeof CHAT_THEMES[number]['id'];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { ConversationResponse } from '../types/dto';
|
||||
import { messageService } from '../services/messageService';
|
||||
import { getConversationListCache } from '../services/database';
|
||||
import { conversationRepository } from '@/database';
|
||||
|
||||
export const CONVERSATION_LIST_PAGE_SIZE = 20;
|
||||
|
||||
@@ -94,7 +94,7 @@ export class SqliteConversationListPagedSource implements IConversationListPaged
|
||||
}
|
||||
this.consumed = true;
|
||||
try {
|
||||
const items = await getConversationListCache();
|
||||
const items = await conversationRepository.getListCache();
|
||||
return { items, hasMore: false };
|
||||
} catch (e) {
|
||||
console.warn('[SqliteConversationListPagedSource] 读取会话列表缓存失败:', e);
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* GroupManager - 蝢斤<E89DA2>蝞∠<E89D9E><E288A0>詨<EFBFBD>璅∪<E79285>嚗<EFBFBD><E59A97><EFBFBD><EFBFBD><EFBFBD>嚗?
|
||||
*
|
||||
* 雿輻鍂 Zustand store 餈𥡝<E9A488><F0A5A19D>嗆<EFBFBD><E59786>恣<EFBFBD>?
|
||||
* 靽脲<E99DBD>銝擧唂 API <20><><EFBFBD><EFBFBD>𤾸<EFBFBD>摰?
|
||||
*/
|
||||
|
||||
import type {
|
||||
GroupListResponse,
|
||||
GroupMemberListResponse,
|
||||
@@ -12,15 +5,7 @@ import type {
|
||||
GroupResponse,
|
||||
} from '../../types/dto';
|
||||
import { groupService } from '../../services/groupService';
|
||||
import {
|
||||
getAllGroupsCache,
|
||||
getGroupCache,
|
||||
getGroupMembersCache,
|
||||
saveGroupCache,
|
||||
saveGroupMembersCache,
|
||||
saveGroupsCache,
|
||||
saveUsersCache,
|
||||
} from '../../services/database';
|
||||
import { groupCacheRepository, userCacheRepository } from '@/database';
|
||||
import { useGroupManagerStore } from './groupStore';
|
||||
import { createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
|
||||
import {
|
||||
@@ -33,8 +18,6 @@ import {
|
||||
GROUP_MEMBER_LIST_PAGE_SIZE,
|
||||
} from '../groupListSources';
|
||||
|
||||
// ==================== 颲<>𨭌<EFBFBD>賣㺭 ====================
|
||||
|
||||
function buildMembersKey(groupId: string, page: number, pageSize: number): string {
|
||||
return `members:${groupId}:${page}:${pageSize}`;
|
||||
}
|
||||
@@ -63,14 +46,7 @@ function buildMemberListResponse(
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== GroupManager 蝐?====================
|
||||
|
||||
class GroupManager {
|
||||
// ==================== 蝢斤<E89DA2><E696A4>𡑒”<F0A19192>詨<EFBFBD> ====================
|
||||
|
||||
/**
|
||||
* <20>瑕<EFBFBD>蝢斤<E89DA2><E696A4>𡑒”
|
||||
*/
|
||||
async getGroups(
|
||||
page = 1,
|
||||
pageSize = GROUP_LIST_PAGE_SIZE,
|
||||
@@ -79,20 +55,17 @@ class GroupManager {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const entry = store.groupsListEntry;
|
||||
|
||||
// 蝚砌<E89D9A>憿萎<E686BF><E8908E>㗇<EFBFBD><E39787><EFBFBD><EFBFBD>摮?
|
||||
if (page === 1 && !forceRefresh && entry && !isCacheExpired(entry)) {
|
||||
return buildGroupListResponse(entry.data, pageSize);
|
||||
}
|
||||
|
||||
// 蝚砌<E89D9A>憿萎<E686BF>蝻枏<E89DBB>餈<EFBFBD><E9A488>嚗𡁜<E59A97><F0A1819C>啣<EFBFBD><E595A3>?
|
||||
if (page === 1 && !forceRefresh && entry && isCacheExpired(entry)) {
|
||||
this.refreshGroupsInBackground(page, pageSize);
|
||||
return buildGroupListResponse(entry.data, pageSize);
|
||||
}
|
||||
|
||||
// 蝚砌<E89D9A>憿蛛<E686BF>撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
|
||||
if (page === 1 && !forceRefresh) {
|
||||
const localGroups = await getAllGroupsCache();
|
||||
const localGroups = await groupCacheRepository.getAll();
|
||||
if (localGroups.length > 0) {
|
||||
const newEntry = createCacheEntry(localGroups, DEFAULT_TTL.GROUP);
|
||||
store.setGroupsListEntry(newEntry);
|
||||
@@ -101,7 +74,6 @@ class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
// <20>𤏸絲霂瑟<E99C82>
|
||||
return this.dedupe(`groups:list:${page}:${pageSize}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getGroups(page, pageSize);
|
||||
@@ -109,7 +81,7 @@ class GroupManager {
|
||||
if (page === 1) {
|
||||
store.setGroupsList(groups);
|
||||
}
|
||||
await saveGroupsCache(groups).catch(() => {});
|
||||
await groupCacheRepository.saveBatch(groups).catch(() => {});
|
||||
return response;
|
||||
});
|
||||
}
|
||||
@@ -122,16 +94,13 @@ class GroupManager {
|
||||
if (page === 1) {
|
||||
store.setGroupsList(groups);
|
||||
}
|
||||
saveGroupsCache(groups).catch(() => {});
|
||||
groupCacheRepository.saveBatch(groups).catch(() => {});
|
||||
return response;
|
||||
}).catch((error) => {
|
||||
console.error('[GroupManager] refreshGroupsInBackground error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <20>𥕦遣蝢斤<E89DA2><E696A4>𡑒”<F0A19192>唳旿皞琜<E79A9E><E7909C>其<EFBFBD> Sources 璅∪<E79285>嚗?
|
||||
*/
|
||||
createGroupListSource(
|
||||
kind: RemoteGroupListSourceKind = 'cursor',
|
||||
pageSize: number = GROUP_LIST_PAGE_SIZE
|
||||
@@ -139,29 +108,21 @@ class GroupManager {
|
||||
return createRemoteGroupListSource(kind, pageSize);
|
||||
}
|
||||
|
||||
// ==================== 蝢斤<E89DA2>霂行<E99C82><E8A18C>詨<EFBFBD> ====================
|
||||
|
||||
/**
|
||||
* <20>瑕<EFBFBD>蝢斤<E89DA2>霂行<E99C82>
|
||||
*/
|
||||
async getGroup(groupId: string, forceRefresh = false): Promise<GroupResponse> {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const entry = store.groupsMap.get(groupId);
|
||||
|
||||
// <20>㗇<EFBFBD>蝻枏<E89DBB>
|
||||
if (!forceRefresh && entry && !isCacheExpired(entry)) {
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
// 餈<><E9A488>蝻枏<E89DBB>嚗𡁜<E59A97><F0A1819C>啣<EFBFBD><E595A3>?
|
||||
if (!forceRefresh && entry && isCacheExpired(entry)) {
|
||||
this.refreshGroupInBackground(groupId);
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
// 撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
|
||||
if (!forceRefresh) {
|
||||
const localGroup = await getGroupCache(groupId);
|
||||
const localGroup = await groupCacheRepository.get(groupId);
|
||||
if (localGroup) {
|
||||
store.setGroup(groupId, localGroup);
|
||||
this.refreshGroupInBackground(groupId);
|
||||
@@ -169,12 +130,11 @@ class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
// <20>𤏸絲霂瑟<E99C82>
|
||||
return this.dedupe(`groups:detail:${groupId}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const group = await groupService.getGroup(groupId);
|
||||
store.setGroup(groupId, group);
|
||||
await saveGroupCache(group).catch(() => {});
|
||||
await groupCacheRepository.save(group).catch(() => {});
|
||||
return group;
|
||||
});
|
||||
}
|
||||
@@ -184,18 +144,13 @@ class GroupManager {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const group = await groupService.getGroup(groupId);
|
||||
store.setGroup(groupId, group);
|
||||
saveGroupCache(group).catch(() => {});
|
||||
groupCacheRepository.save(group).catch(() => {});
|
||||
return group;
|
||||
}).catch((error) => {
|
||||
console.error('[GroupManager] refreshGroupInBackground error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC>詨<EFBFBD> ====================
|
||||
|
||||
/**
|
||||
* <20>瑕<EFBFBD>蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC>𡑒”
|
||||
*/
|
||||
async getMembers(
|
||||
groupId: string,
|
||||
page = 1,
|
||||
@@ -206,20 +161,17 @@ class GroupManager {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const entry = store.membersMap.get(key);
|
||||
|
||||
// <20>㗇<EFBFBD>蝻枏<E89DBB>
|
||||
if (!forceRefresh && entry && !isCacheExpired(entry)) {
|
||||
return buildMemberListResponse(entry.data, page, pageSize);
|
||||
}
|
||||
|
||||
// 餈<><E9A488>蝻枏<E89DBB>嚗𡁜<E59A97><F0A1819C>啣<EFBFBD><E595A3>?
|
||||
if (!forceRefresh && entry && isCacheExpired(entry)) {
|
||||
this.refreshMembersInBackground(groupId, page, pageSize);
|
||||
return buildMemberListResponse(entry.data, page, pageSize);
|
||||
}
|
||||
|
||||
// 蝚砌<E89D9A>憿蛛<E686BF>撠肽<E692A0><E882BD>砍𧑐蝻枏<E89DBB>
|
||||
if (page === 1 && !forceRefresh) {
|
||||
const localMembers = await getGroupMembersCache(groupId);
|
||||
const localMembers = await groupCacheRepository.getMembers(groupId);
|
||||
if (localMembers.length > 0) {
|
||||
store.setMembers(groupId, localMembers, page, pageSize);
|
||||
this.refreshMembersInBackground(groupId, page, pageSize);
|
||||
@@ -227,16 +179,15 @@ class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
// <20>𤏸絲霂瑟<E99C82>
|
||||
return this.dedupe(`groups:members:${key}`, async () => {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const response = await groupService.getMembers(groupId, page, pageSize);
|
||||
const members = response.list || [];
|
||||
store.setMembers(groupId, members, page, pageSize);
|
||||
if (page === 1) {
|
||||
await saveGroupMembersCache(groupId, members).catch(() => {});
|
||||
await groupCacheRepository.saveMembers(groupId, members).catch(() => {});
|
||||
}
|
||||
await saveUsersCache(
|
||||
await userCacheRepository.saveBatch(
|
||||
members
|
||||
.map((member) => member.user)
|
||||
.filter((user): user is NonNullable<typeof user> => Boolean(user))
|
||||
@@ -257,9 +208,9 @@ class GroupManager {
|
||||
const members = response.list || [];
|
||||
store.setMembers(groupId, members, page, pageSize);
|
||||
if (page === 1) {
|
||||
saveGroupMembersCache(groupId, members).catch(() => {});
|
||||
groupCacheRepository.saveMembers(groupId, members).catch(() => {});
|
||||
}
|
||||
saveUsersCache(
|
||||
userCacheRepository.saveBatch(
|
||||
members
|
||||
.map((member) => member.user)
|
||||
.filter((user): user is NonNullable<typeof user> => Boolean(user))
|
||||
@@ -270,9 +221,6 @@ class GroupManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <20>𥕦遣蝢斤<E89DA2><E696A4>𣂼<EFBFBD><F0A382BC>𡑒”<F0A19192>唳旿皞琜<E79A9E><E7909C>其<EFBFBD> Sources 璅∪<E79285>嚗?
|
||||
*/
|
||||
createGroupMemberListSource(
|
||||
groupId: string,
|
||||
kind: RemoteGroupListSourceKind = 'cursor',
|
||||
@@ -281,11 +229,6 @@ class GroupManager {
|
||||
return createRemoteGroupMemberListSource(groupId, kind, pageSize);
|
||||
}
|
||||
|
||||
// ==================== 蝻枏<E89DBB>蝞∠<E89D9E> ====================
|
||||
|
||||
/**
|
||||
* 雿輻<E99BBF>摮睃仃<E79D83>?
|
||||
*/
|
||||
invalidate(groupId?: string): void {
|
||||
if (!groupId) {
|
||||
useGroupManagerStore.getState().invalidateAll();
|
||||
@@ -294,15 +237,10 @@ class GroupManager {
|
||||
useGroupManagerStore.getState().invalidateGroup(groupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 皜<>膄<EFBFBD><E88684><EFBFBD>㗇㺭<E39787>?
|
||||
*/
|
||||
clear(): void {
|
||||
useGroupManagerStore.getState().reset();
|
||||
}
|
||||
|
||||
// ==================== 霂瑟<E99C82><E7919F>駁<EFBFBD> ====================
|
||||
|
||||
private async dedupe<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
|
||||
const store = useGroupManagerStore.getState();
|
||||
const pending = store.getPendingRequest<T>(key);
|
||||
@@ -316,11 +254,8 @@ class GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== <20>蓥<EFBFBD>撖澆枂 ====================
|
||||
|
||||
export const groupManager = new GroupManager();
|
||||
|
||||
// 撖澆枂蝐颱誑<E9A2B1>舀<EFBFBD>蝐餃<E89D90>璉<EFBFBD><E79289>亙<EFBFBD>瘚贝<E7989A>
|
||||
export { GroupManager };
|
||||
|
||||
export default groupManager;
|
||||
export default groupManager;
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
CursorPaginationRequest,
|
||||
} from '../types/dto';
|
||||
import { groupService } from '../services/groupService';
|
||||
import { getAllGroupsCache, getGroupMembersCache } from '../services/database';
|
||||
import { groupCacheRepository } from '@/database';
|
||||
|
||||
export const GROUP_LIST_PAGE_SIZE = 20;
|
||||
export const GROUP_MEMBER_LIST_PAGE_SIZE = 50;
|
||||
@@ -130,7 +130,7 @@ export class SqliteGroupListPagedSource implements IGroupListPagedSource {
|
||||
}
|
||||
this.consumed = true;
|
||||
try {
|
||||
const items = await getAllGroupsCache();
|
||||
const items = await groupCacheRepository.getAll();
|
||||
return { items, hasMore: false };
|
||||
} catch (e) {
|
||||
console.warn('[SqliteGroupListPagedSource] 读取群组列表缓存失败:', e);
|
||||
@@ -265,7 +265,7 @@ export class SqliteGroupMemberListPagedSource implements IGroupMemberListPagedSo
|
||||
}
|
||||
this.consumed = true;
|
||||
try {
|
||||
const items = await getGroupMembersCache(this.groupId);
|
||||
const items = await groupCacheRepository.getMembers(this.groupId);
|
||||
return { items, hasMore: false };
|
||||
} catch (e) {
|
||||
console.warn('[SqliteGroupMemberListPagedSource] 读取成员列表缓存失败:', e);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GroupMemberResponse, UserDTO } from '../types/dto';
|
||||
import { getUserCache } from '../services/database';
|
||||
import { userCacheRepository } from '@/database';
|
||||
import { userManager } from './userManager';
|
||||
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ export async function enrichGroupMembersWithUserProfiles(
|
||||
await Promise.all(
|
||||
userIds.map(async (userId) => {
|
||||
try {
|
||||
const cachedUser = await getUserCache(userId);
|
||||
const cachedUser = await userCacheRepository.get(userId);
|
||||
if (cachedUser) {
|
||||
userMap.set(userId, cachedUser);
|
||||
return;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import type { ConversationResponse } from '../../../types/dto';
|
||||
import { messageService } from '../../../services/messageService';
|
||||
import { deleteConversation } from '../../../services/database';
|
||||
import { conversationRepository } from '@/database';
|
||||
import type { IConversationOperations } from '../types';
|
||||
import { useMessageStore, normalizeConversationId } from '../store';
|
||||
|
||||
@@ -60,7 +60,7 @@ export class ConversationOperations implements IConversationOperations {
|
||||
store.removeConversation(conversationId);
|
||||
|
||||
// 删除本地数据库中的会话
|
||||
deleteConversation(conversationId).catch(error => {
|
||||
conversationRepository.delete(conversationId).catch(error => {
|
||||
console.error('[ConversationOperations] 删除本地会话失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import type { MessageResponse, MessageSegment, ConversationResponse } from '../../../types/dto';
|
||||
import { messageService } from '../../../services/messageService';
|
||||
import { saveMessage } from '../../../services/database';
|
||||
import { messageRepository } from '@/database';
|
||||
import type { IMessageSendService } from '../types';
|
||||
import { useMessageStore, mergeMessagesById } from '../store';
|
||||
|
||||
@@ -64,7 +64,7 @@ export class MessageSendService implements IMessageSendService {
|
||||
.map((s: any) => s.data?.text || '')
|
||||
.join('') || '';
|
||||
|
||||
saveMessage({
|
||||
messageRepository.saveMessage({
|
||||
id: response.id,
|
||||
conversationId,
|
||||
senderId: currentUserId || '',
|
||||
|
||||
@@ -9,13 +9,7 @@
|
||||
|
||||
import type { ConversationResponse, MessageResponse } from '../../../types/dto';
|
||||
import { messageService } from '../../../services/messageService';
|
||||
import {
|
||||
getMessagesByConversation,
|
||||
getMaxSeq,
|
||||
saveMessagesBatch,
|
||||
saveConversationsWithRelatedCache,
|
||||
getMessagesBeforeSeq,
|
||||
} from '../../../services/database';
|
||||
import { messageRepository, conversationRepository } from '@/database';
|
||||
import {
|
||||
type IConversationListPagedSource,
|
||||
SqliteConversationListPagedSource,
|
||||
@@ -213,8 +207,8 @@ export class MessageSyncService implements IMessageSyncService {
|
||||
// 先从本地数据库加载
|
||||
if (!hasInMemoryMessages) {
|
||||
try {
|
||||
const localMessages = await getMessagesByConversation(conversationId, 20);
|
||||
const localMaxSeq = await getMaxSeq(conversationId);
|
||||
const localMessages = await messageRepository.getByConversation(conversationId, 20);
|
||||
const localMaxSeq = await messageRepository.getMaxSeq(conversationId);
|
||||
baselineMaxSeq = localMaxSeq;
|
||||
|
||||
if (localMessages.length > 0) {
|
||||
@@ -249,7 +243,7 @@ export class MessageSyncService implements IMessageSyncService {
|
||||
store.setMessages(conversationId, mergedSnapshot);
|
||||
|
||||
// 持久化到本地
|
||||
saveMessagesBatch(snapshotMessages.map((m: any) => ({
|
||||
messageRepository.saveMessagesBatch(snapshotMessages.map((m: any) => ({
|
||||
id: m.id,
|
||||
conversationId: m.conversation_id || conversationId,
|
||||
senderId: m.sender_id,
|
||||
@@ -276,7 +270,7 @@ export class MessageSyncService implements IMessageSyncService {
|
||||
const mergedMessages = mergeMessagesById(existingMessages, newMessages);
|
||||
store.setMessages(conversationId, mergedMessages);
|
||||
|
||||
saveMessagesBatch(newMessages.map((m: any) => ({
|
||||
messageRepository.saveMessagesBatch(newMessages.map((m: any) => ({
|
||||
id: m.id,
|
||||
conversationId: m.conversation_id || conversationId,
|
||||
senderId: m.sender_id,
|
||||
@@ -306,20 +300,20 @@ export class MessageSyncService implements IMessageSyncService {
|
||||
|
||||
store.setMessages(conversationId, mergedMessages);
|
||||
|
||||
saveMessagesBatch(newMessages.map((m: any) => ({
|
||||
id: m.id,
|
||||
conversationId: m.conversation_id || conversationId,
|
||||
senderId: m.sender_id,
|
||||
content: m.content,
|
||||
type: m.type || 'text',
|
||||
isRead: m.is_read || false,
|
||||
createdAt: m.created_at,
|
||||
seq: m.seq,
|
||||
status: m.status || 'normal',
|
||||
segments: m.segments,
|
||||
}))).catch(error => {
|
||||
console.error('[MessageSyncService] 保存消息到本地失败:', error);
|
||||
});
|
||||
messageRepository.saveMessagesBatch(newMessages.map((m: any) => ({
|
||||
id: m.id,
|
||||
conversationId: m.conversation_id || conversationId,
|
||||
senderId: m.sender_id,
|
||||
content: m.content,
|
||||
type: m.type || 'text',
|
||||
isRead: m.is_read || false,
|
||||
createdAt: m.created_at,
|
||||
seq: m.seq,
|
||||
status: m.status || 'normal',
|
||||
segments: m.segments,
|
||||
}))).catch(error => {
|
||||
console.error('[MessageSyncService] 保存消息到本地失败:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -348,7 +342,7 @@ export class MessageSyncService implements IMessageSyncService {
|
||||
|
||||
try {
|
||||
// 先从本地获取
|
||||
const localMessages = await getMessagesBeforeSeq(conversationId, beforeSeq, limit);
|
||||
const localMessages = await messageRepository.getBeforeSeq(conversationId, beforeSeq, limit);
|
||||
|
||||
if (localMessages.length >= limit) {
|
||||
const formattedMessages: MessageResponse[] = [...localMessages].reverse().map(m => ({
|
||||
@@ -374,7 +368,7 @@ export class MessageSyncService implements IMessageSyncService {
|
||||
if (response?.messages && response.messages.length > 0) {
|
||||
const serverMessages = response.messages;
|
||||
|
||||
await saveMessagesBatch(serverMessages.map((m: any) => ({
|
||||
await messageRepository.saveMessagesBatch(serverMessages.map((m: any) => ({
|
||||
id: m.id,
|
||||
conversationId: m.conversation_id || conversationId,
|
||||
senderId: m.sender_id,
|
||||
@@ -497,7 +491,7 @@ export class MessageSyncService implements IMessageSyncService {
|
||||
.map(conv => conv.group)
|
||||
.filter((group): group is NonNullable<ConversationResponse['group']> => Boolean(group));
|
||||
|
||||
saveConversationsWithRelatedCache(list, users, groups).catch(error => {
|
||||
conversationRepository.saveWithRelated(list, users, groups).catch(error => {
|
||||
console.error('[MessageSyncService] 持久化会话列表失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import type { ConversationResponse } from '../../../types/dto';
|
||||
import { messageService } from '../../../services/messageService';
|
||||
import { markConversationAsRead, updateConversationCacheUnreadCount } from '../../../services/database';
|
||||
import { messageRepository, conversationRepository } from '@/database';
|
||||
import type { ReadStateRecord, IReadReceiptManager } from '../types';
|
||||
import { READ_STATE_PROTECTION_DELAY } from '../constants';
|
||||
import { useMessageStore, normalizeConversationId } from '../store';
|
||||
@@ -78,7 +78,7 @@ export class ReadReceiptManager implements IReadReceiptManager {
|
||||
store.setUnreadCount(newTotalUnread, currentUnread.system);
|
||||
|
||||
// 3. 更新本地数据库
|
||||
markConversationAsRead(normalizedId).catch(console.error);
|
||||
messageRepository.markConversationAsRead(normalizedId).catch(console.error);
|
||||
|
||||
// 4. 调用 API,完成后设置延迟清除保护
|
||||
try {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import type { MessageResponse, UserDTO } from '../../../types/dto';
|
||||
import { api } from '../../../services/api';
|
||||
import { getUserCache, saveUserCache } from '../../../services/database';
|
||||
import { userCacheRepository } from '@/database';
|
||||
import type { IUserCacheService } from '../types';
|
||||
|
||||
export class UserCacheService implements IUserCacheService {
|
||||
@@ -17,7 +17,7 @@ export class UserCacheService implements IUserCacheService {
|
||||
*/
|
||||
async getSenderInfo(userId: string): Promise<UserDTO | null> {
|
||||
// 1. 先检查本地缓存
|
||||
const cachedUser = await getUserCache(userId);
|
||||
const cachedUser = await userCacheRepository.get(userId);
|
||||
if (cachedUser) {
|
||||
return cachedUser;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export class UserCacheService implements IUserCacheService {
|
||||
const response = await api.get<UserDTO>(`/users/${userId}`);
|
||||
if (response.code === 0 && response.data) {
|
||||
// 缓存到本地数据库
|
||||
await saveUserCache(response.data);
|
||||
await userCacheRepository.save(response.data);
|
||||
return response.data;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
} from '../../../services/wsService';
|
||||
import { wsService, GroupNoticeType } from '../../../services/wsService';
|
||||
import { vibrateOnMessage } from '../../../services/messageVibrationService';
|
||||
import { saveMessage, updateMessageStatus } from '../../../services/database';
|
||||
import { messageRepository } from '@/database';
|
||||
import type { MessageResponse, ConversationResponse } from '../../../types/dto';
|
||||
import type {
|
||||
IWSMessageHandler,
|
||||
@@ -318,7 +318,7 @@ export class WSMessageHandler implements IWSMessageHandler {
|
||||
|
||||
// 异步保存到本地数据库
|
||||
const textContent = segments?.filter((s: any) => s.type === 'text').map((s: any) => s.data?.text || '').join('') || '';
|
||||
saveMessage({
|
||||
messageRepository.saveMessage({
|
||||
id,
|
||||
conversationId: normalizedConversationId,
|
||||
senderId: sender_id || '',
|
||||
@@ -358,7 +358,7 @@ export class WSMessageHandler implements IWSMessageHandler {
|
||||
this.markMessageAsRecalled(normalizedConversationId, message_id);
|
||||
this.syncConversationLastMessageOnRecall(normalizedConversationId, message_id);
|
||||
|
||||
updateMessageStatus(message_id, 'recalled', true).catch(error => {
|
||||
messageRepository.updateStatus(message_id, 'recalled', true).catch(error => {
|
||||
console.error('[WSMessageHandler] 更新本地消息撤回状态失败:', error);
|
||||
});
|
||||
}
|
||||
@@ -373,7 +373,7 @@ export class WSMessageHandler implements IWSMessageHandler {
|
||||
this.markMessageAsRecalled(normalizedConversationId, message_id);
|
||||
this.syncConversationLastMessageOnRecall(normalizedConversationId, message_id);
|
||||
|
||||
updateMessageStatus(message_id, 'recalled', true).catch(error => {
|
||||
messageRepository.updateStatus(message_id, 'recalled', true).catch(error => {
|
||||
console.error('[WSMessageHandler] 更新本地消息撤回状态失败:', error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,46 +1,34 @@
|
||||
/**
|
||||
* UserManager - 用户管ç<EFBFBD>†æ ¸å¿ƒæ¨¡å<EFBFBD>—(é‡<EFBFBD>构版ï¼?
|
||||
* UserManager - 用户管理核心模块(重构版)
|
||||
*
|
||||
* 使用 Zustand store 进行状æ€<EFBFBD>管ç<EFBFBD>?
|
||||
* ä¿<EFBFBD>æŒ<EFBFBD>与旧 API çš„å<E2809E>‘å<E28098>Žå…¼å®?
|
||||
* 使用 Zustand store 进行状态管理
|
||||
* 保持与旧 API 的向后兼容
|
||||
*/
|
||||
|
||||
import type { UserDTO } from '../../types/dto';
|
||||
import { authService } from '../../services/authService';
|
||||
import {
|
||||
getCurrentUserCache,
|
||||
getUserCache,
|
||||
saveCurrentUserCache,
|
||||
saveUserCache,
|
||||
} from '../../services/database';
|
||||
import { userCacheRepository } from '@/database';
|
||||
import { useUserManagerStore } from './userStore';
|
||||
import { createCacheEntry, isCacheExpired, DEFAULT_TTL } from '../utils/cache';
|
||||
|
||||
// ==================== UserManager ç±?====================
|
||||
// ==================== UserManager 类 ====================
|
||||
|
||||
class UserManager {
|
||||
/**
|
||||
* 获å<C2B7>–当å‰<C3A5>用户
|
||||
* 支æŒ<C3A6>缓å˜è¿‡æœŸæ£€æŸ¥å’Œå<C592>Žå<C5BD>°åˆ·æ–°
|
||||
*/
|
||||
async getCurrentUser(forceRefresh = false): Promise<UserDTO | null> {
|
||||
const store = useUserManagerStore.getState();
|
||||
const entry = store.currentUserEntry;
|
||||
|
||||
// 有效缓å˜
|
||||
if (!forceRefresh && entry && !isCacheExpired(entry)) {
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
// 过期缓å˜ï¼šå<C5A1>Žå<C5BD>°åˆ·æ–°ï¼Œç«‹å<E280B9>³è¿”回旧数æ<C2B0>?
|
||||
if (!forceRefresh && entry && isCacheExpired(entry)) {
|
||||
this.refreshCurrentUserInBackground();
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
// å°<C3A5>试从本地数æ<C2B0>®åº“åŠ è½½
|
||||
if (!forceRefresh) {
|
||||
const cachedFromDb = await getCurrentUserCache();
|
||||
const cachedFromDb = await userCacheRepository.getCurrent();
|
||||
if (cachedFromDb) {
|
||||
const newEntry = createCacheEntry(cachedFromDb, DEFAULT_TTL.USER);
|
||||
store.setCurrentUserEntry(newEntry);
|
||||
@@ -49,23 +37,19 @@ class UserManager {
|
||||
}
|
||||
}
|
||||
|
||||
// å<>‘èµ· API 请求
|
||||
return this.dedupe('users:current', async () => {
|
||||
const user = await authService.fetchCurrentUserFromAPI();
|
||||
const newEntry = createCacheEntry(user, DEFAULT_TTL.USER);
|
||||
store.setCurrentUserEntry(newEntry);
|
||||
|
||||
if (user) {
|
||||
saveCurrentUserCache(user).catch(() => {});
|
||||
saveUserCache(user).catch(() => {});
|
||||
userCacheRepository.saveCurrent(user).catch(() => {});
|
||||
userCacheRepository.save(user).catch(() => {});
|
||||
}
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* å<>Žå<C5BD>°åˆ·æ–°å½“å‰<C3A5>用户
|
||||
*/
|
||||
private refreshCurrentUserInBackground(): void {
|
||||
this.dedupe('users:current:bg', async () => {
|
||||
const store = useUserManagerStore.getState();
|
||||
@@ -74,8 +58,8 @@ class UserManager {
|
||||
store.setCurrentUserEntry(newEntry);
|
||||
|
||||
if (user) {
|
||||
saveCurrentUserCache(user).catch(() => {});
|
||||
saveUserCache(user).catch(() => {});
|
||||
userCacheRepository.saveCurrent(user).catch(() => {});
|
||||
userCacheRepository.save(user).catch(() => {});
|
||||
}
|
||||
return user;
|
||||
}).catch((error) => {
|
||||
@@ -83,27 +67,21 @@ class UserManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* æ ¹æ<C2B9>® ID 获å<C2B7>–用户
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// 过期缓å˜ï¼šå<C5A1>Žå<C5BD>°åˆ·æ–°ï¼Œç«‹å<E280B9>³è¿”回旧数æ<C2B0>?
|
||||
if (!forceRefresh && entry && isCacheExpired(entry)) {
|
||||
this.refreshUserByIdInBackground(userId);
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
// å°<C3A5>试从本地数æ<C2B0>®åº“åŠ è½½
|
||||
if (!forceRefresh) {
|
||||
const cachedFromDb = await getUserCache(userId);
|
||||
const cachedFromDb = await userCacheRepository.get(userId);
|
||||
if (cachedFromDb) {
|
||||
const newEntry = createCacheEntry(cachedFromDb, DEFAULT_TTL.USER);
|
||||
store.setUser(userId, cachedFromDb);
|
||||
@@ -112,7 +90,6 @@ class UserManager {
|
||||
}
|
||||
}
|
||||
|
||||
// å<>‘èµ· API 请求
|
||||
return this.dedupe(`users:detail:${userId}`, async () => {
|
||||
const store = useUserManagerStore.getState();
|
||||
const user = await authService.fetchUserByIdFromAPI(userId);
|
||||
@@ -120,15 +97,12 @@ class UserManager {
|
||||
store.setUser(userId, user);
|
||||
|
||||
if (user) {
|
||||
saveUserCache(user).catch(() => {});
|
||||
userCacheRepository.save(user).catch(() => {});
|
||||
}
|
||||
return user;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* å<>Žå<C5BD>°åˆ·æ–°ç”¨æˆ·
|
||||
*/
|
||||
private refreshUserByIdInBackground(userId: string): void {
|
||||
this.dedupe(`users:detail:bg:${userId}`, async () => {
|
||||
const store = useUserManagerStore.getState();
|
||||
@@ -137,7 +111,7 @@ class UserManager {
|
||||
store.setUser(userId, user);
|
||||
|
||||
if (user) {
|
||||
saveUserCache(user).catch(() => {});
|
||||
userCacheRepository.save(user).catch(() => {});
|
||||
}
|
||||
return user;
|
||||
}).catch((error) => {
|
||||
@@ -145,16 +119,10 @@ class UserManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使缓å˜å¤±æ•?
|
||||
*/
|
||||
invalidateUsers(): void {
|
||||
useUserManagerStore.getState().invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求去é‡<C3A9>
|
||||
*/
|
||||
private async dedupe<T>(key: string, fetcher: () => Promise<T>): Promise<T> {
|
||||
const store = useUserManagerStore.getState();
|
||||
const pending = store.getPendingRequest<T>(key);
|
||||
@@ -168,11 +136,8 @@ class UserManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== å<>•例导出 ====================
|
||||
|
||||
export const userManager = new UserManager();
|
||||
|
||||
// 导出类以支æŒ<C3A6>类型检查和测试
|
||||
export { UserManager };
|
||||
|
||||
export default userManager;
|
||||
export default userManager;
|
||||
Reference in New Issue
Block a user