This commit is contained in:
2026-04-25 16:36:34 +08:00
commit db90e7579b
1876 changed files with 189777 additions and 0 deletions

20
client/shared/cache/Provider.tsx vendored Normal file
View File

@@ -0,0 +1,20 @@
import React, { PropsWithChildren } from 'react';
import { asyncStoragePersister, queryClient } from './';
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
/**
* 缓存上下文
*/
export const CacheProvider: React.FC<PropsWithChildren> = React.memo(
(props) => {
return (
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister: asyncStoragePersister }}
>
{props.children}
</PersistQueryClientProvider>
);
}
);
CacheProvider.displayName = 'CacheProvider';

172
client/shared/cache/cache.ts vendored Normal file
View File

@@ -0,0 +1,172 @@
import {
ChatConverseInfo,
fetchConverseInfo,
getConverseAckInfo,
} from '../model/converse';
import {
findGroupInviteByCode,
getGroupBasicInfo,
GroupBasicInfo,
GroupInvite,
} from '../model/group';
import { getConverseLastMessageInfo } from '../model/message';
import {
fetchLocalStaticRegistryPlugins,
fetchRegistryPlugins,
fetchServiceRegistryPlugins,
PluginManifest,
} from '../model/plugin';
import { fetchUserInfo, getUserSettings, UserBaseInfo } from '../model/user';
import { parseUrlStr } from '../utils/url-helper';
import { queryClient } from './index';
export enum CacheKey {
user = 'user',
converse = 'converse',
converseAck = 'converseAck',
baseGroupInfo = 'baseGroupInfo',
groupInvite = 'groupInvite',
pluginRegistry = 'pluginRegistry',
userSettings = 'userSettings',
}
/**
* 获取缓存的用户信息
*/
export async function getCachedUserInfo(
userId: string,
refetch = false
): Promise<UserBaseInfo> {
const data = await queryClient.fetchQuery(
[CacheKey.user, userId],
() => fetchUserInfo(userId),
{
staleTime: refetch ? 0 : 2 * 60 * 60 * 1000, // 缓存2小时
}
);
return data;
}
/**
* 获取缓存的会话信息
*/
export async function getCachedConverseInfo(
converseId: string
): Promise<ChatConverseInfo> {
const data = await queryClient.fetchQuery(
[CacheKey.converse, converseId],
() => fetchConverseInfo(converseId)
);
return data;
}
/**
* 获取缓存的邀请码信息
*/
export async function getCachedBaseGroupInfo(
groupId: string
): Promise<GroupBasicInfo | null> {
const data = await queryClient.fetchQuery(
[CacheKey.baseGroupInfo, groupId],
() => getGroupBasicInfo(groupId)
);
return data;
}
/**
* 获取缓存的邀请码信息
*/
export async function getCachedGroupInviteInfo(
inviteCode: string
): Promise<GroupInvite | null> {
const data = await queryClient.fetchQuery(
[CacheKey.groupInvite, inviteCode],
() => findGroupInviteByCode(inviteCode)
);
return data;
}
/**
* 获取缓存的用户信息
*/
export async function getCachedAckInfo(converseId: string, refetch = false) {
const data = await queryClient.fetchQuery(
[CacheKey.converseAck, converseId],
() => {
return Promise.all([
getConverseAckInfo([converseId]).then((d) => d[0]),
getConverseLastMessageInfo([converseId]).then((d) => d[0]),
]).then(([ack, lastMessage]) => {
return {
converseId,
ack,
lastMessage,
};
});
},
{
staleTime: 2 * 1000, // 缓存2s, 减少一秒内的重复请求(无意义)
}
);
return data;
}
/**
* 获取缓存的插件列表
*/
export async function getCachedRegistryPlugins(): Promise<PluginManifest[]> {
const data = await queryClient.fetchQuery(
[CacheKey.pluginRegistry],
() =>
Promise.all([
fetchRegistryPlugins().catch(() => []),
fetchServiceRegistryPlugins()
.then((list) =>
list.map((manifest) => {
const serviceManifest = {
...manifest,
// 后端url策略。根据前端的url在获取时自动变更为当前链接的后端地址
url: parseUrlStr(manifest.url),
};
if (manifest.icon) {
serviceManifest.icon = parseUrlStr(manifest.icon);
}
if (manifest.documentUrl) {
serviceManifest.documentUrl = parseUrlStr(manifest.documentUrl);
}
return serviceManifest;
})
)
.catch(() => []),
fetchLocalStaticRegistryPlugins().catch(() => []),
]).then(([a, b, c]) => [...a, ...b, ...c]),
{
staleTime: 2 * 60 * 60 * 1000, // 缓存2小时
}
);
return data;
}
/**
* 获取用户配置
*/
export async function getCachedUserSettings() {
const data = await queryClient.fetchQuery(
[CacheKey.userSettings],
() => getUserSettings(),
{
staleTime: 10 * 60 * 1000, // 缓存10分钟
}
);
return data;
}

21
client/shared/cache/index.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
import { QueryClient } from '@tanstack/react-query';
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
import { getStorage } from '../manager/storage';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 10 * 1000, // 默认缓存10s
},
},
});
export const asyncStoragePersister = createAsyncStoragePersister({
storage: {
getItem: (key: string) => {
return getStorage().get(key);
},
setItem: (key: string, value: string) => getStorage().set(key, value),
removeItem: (key: string) => getStorage().remove(key),
},
});

30
client/shared/cache/useCache.ts vendored Normal file
View File

@@ -0,0 +1,30 @@
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { getUserOnlineStatus } from '../model/user';
export { useQuery, useQueryClient };
/**
* 用户登录状态
*/
export function useCachedOnlineStatus(
ids: string[],
onOnlineStatusUpdate?: (onlineStatus: boolean[]) => void
): boolean[] {
const staleTime = 20 * 1000; // 缓存20s
const { data, isSuccess } = useQuery(
['onlineStatus', ids.join(',')],
() => getUserOnlineStatus(ids),
{
staleTime,
}
);
if (isSuccess && Array.isArray(data)) {
if (typeof onOnlineStatusUpdate === 'function' && data) {
onOnlineStatusUpdate(data);
}
}
return data ?? ids.map(() => false);
}

48
client/shared/cache/utils.ts vendored Normal file
View File

@@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { FetchQueryOptions } from '@tanstack/react-query';
import { queryClient } from './';
/**
* 构建缓存请求
* TODO: 这里的类型真的不好写, 先用any来过滤内部的, 只保证外部使用ok
*
* @example
* const queryData = buildCachedRequest('key', (arg1, arg2) => {
* return request.post(...)
* })
*/
export function buildCachedRequest<R, F extends (...args: any) => Promise<R>>(
name: string,
fn: F,
options?: FetchQueryOptions
): F & {
/**
* 根据name重新获取数据
*/
refetch: () => Promise<void>;
/**
* 清空name相关缓存
*/
clearCache: () => void;
} {
const req = ((...args: any) => {
return queryClient.fetchQuery(
[name, JSON.stringify(args)],
() => fn(...args),
options
);
}) as any;
const refetch = () => {
return queryClient.refetchQueries([name]);
};
const clearCache = () => {
queryClient.removeQueries([name]);
};
req.refetch = refetch;
req.clearCache = clearCache;
return req;
}