重构数据库管理逻辑,简化用户数据库初始化流程。引入新的数据库管理类以支持动态数据库名称和用户状态管理。更新应用启动逻辑以确保在用户登录时正确初始化数据库。增强持久化数据恢复功能,确保用户数据的可靠性和一致性。
This commit is contained in:
@@ -7,97 +7,18 @@ import dayjs from "dayjs";
|
|||||||
import "dayjs/locale/zh-cn";
|
import "dayjs/locale/zh-cn";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./styles/global.scss";
|
import "./styles/global.scss";
|
||||||
import { db } from "./utils/db"; // 引入数据库实例
|
import { initializeDatabaseFromPersistedUser } from "./utils/db";
|
||||||
|
|
||||||
// 设置dayjs为中文
|
// 设置dayjs为中文
|
||||||
dayjs.locale("zh-cn");
|
dayjs.locale("zh-cn");
|
||||||
|
|
||||||
// 清理旧数据库
|
async function bootstrap() {
|
||||||
async function cleanupOldDatabase() {
|
|
||||||
try {
|
try {
|
||||||
// 获取所有数据库
|
await initializeDatabaseFromPersistedUser();
|
||||||
const databases = await indexedDB.databases();
|
|
||||||
|
|
||||||
for (const dbInfo of databases) {
|
|
||||||
if (dbInfo.name === "CunkebaoDatabase") {
|
|
||||||
console.log("检测到旧版数据库,开始清理...");
|
|
||||||
|
|
||||||
// 打开数据库检查版本
|
|
||||||
const openRequest = indexedDB.open(dbInfo.name);
|
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
openRequest.onsuccess = async event => {
|
|
||||||
const database = (event.target as IDBOpenDBRequest).result;
|
|
||||||
const objectStoreNames = Array.from(database.objectStoreNames);
|
|
||||||
|
|
||||||
// 检查是否存在旧表
|
|
||||||
const hasOldTables = objectStoreNames.some(name =>
|
|
||||||
[
|
|
||||||
"kfUsers",
|
|
||||||
"weChatGroup",
|
|
||||||
"contracts",
|
|
||||||
"newContactList",
|
|
||||||
"messageList",
|
|
||||||
].includes(name),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasOldTables) {
|
|
||||||
console.log("发现旧表,删除整个数据库:", objectStoreNames);
|
|
||||||
database.close();
|
|
||||||
|
|
||||||
// 删除整个数据库
|
|
||||||
const deleteRequest = indexedDB.deleteDatabase(dbInfo.name);
|
|
||||||
deleteRequest.onsuccess = () => {
|
|
||||||
console.log("旧数据库已删除");
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
deleteRequest.onerror = () => {
|
|
||||||
console.error("删除旧数据库失败");
|
|
||||||
reject();
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
console.log("数据库结构正确,无需清理");
|
|
||||||
database.close();
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
openRequest.onerror = () => {
|
|
||||||
console.error("无法打开数据库进行检查");
|
|
||||||
reject();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("清理旧数据库时出错(可忽略):", error);
|
console.warn("Failed to prepare database before app bootstrap:", error);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据库初始化
|
|
||||||
async function initializeApp() {
|
|
||||||
try {
|
|
||||||
// 1. 清理旧数据库
|
|
||||||
await cleanupOldDatabase();
|
|
||||||
|
|
||||||
// 2. 打开新数据库
|
|
||||||
await db.open();
|
|
||||||
console.log("数据库初始化成功");
|
|
||||||
|
|
||||||
// 3. 开发环境清空数据(可选)
|
|
||||||
if (process.env.NODE_ENV === "development") {
|
|
||||||
console.log("开发环境:跳过数据清理");
|
|
||||||
// 如需清空数据,取消下面的注释
|
|
||||||
// await db.chatSessions.clear();
|
|
||||||
// await db.contactsUnified.clear();
|
|
||||||
// await db.contactLabelMap.clear();
|
|
||||||
// await db.userLoginRecords.clear();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("数据库初始化失败:", error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 渲染应用
|
|
||||||
const root = createRoot(document.getElementById("root")!);
|
const root = createRoot(document.getElementById("root")!);
|
||||||
root.render(
|
root.render(
|
||||||
<ConfigProvider locale={zhCN}>
|
<ConfigProvider locale={zhCN}>
|
||||||
@@ -106,5 +27,4 @@ async function initializeApp() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动应用
|
void bootstrap();
|
||||||
initializeApp();
|
|
||||||
|
|||||||
@@ -33,5 +33,5 @@ export interface GetGroupPushHistoryParams {
|
|||||||
[property: string]: any;
|
[property: string]: any;
|
||||||
}
|
}
|
||||||
export const getPushHistory = async (params: GetGroupPushHistoryParams) => {
|
export const getPushHistory = async (params: GetGroupPushHistoryParams) => {
|
||||||
return request("/v1/workbench/group-push-history", { params });
|
return request("/v1/workbench/group-push-history", params, "GET");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useCallback, useMemo, useState } from "react";
|
||||||
import { Layout, Tabs } from "antd";
|
import { Layout, Tabs } from "antd";
|
||||||
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
|
||||||
import styles from "./Person.module.scss";
|
import styles from "./Person.module.scss";
|
||||||
@@ -9,6 +9,8 @@ import LayoutFiexd from "@/components/Layout/LayoutFiexd";
|
|||||||
|
|
||||||
const { Sider } = Layout;
|
const { Sider } = Layout;
|
||||||
|
|
||||||
|
const noop = () => {};
|
||||||
|
|
||||||
interface PersonProps {
|
interface PersonProps {
|
||||||
contract: ContractData | weChatGroup;
|
contract: ContractData | weChatGroup;
|
||||||
}
|
}
|
||||||
@@ -16,47 +18,46 @@ interface PersonProps {
|
|||||||
const Person: React.FC<PersonProps> = ({ contract }) => {
|
const Person: React.FC<PersonProps> = ({ contract }) => {
|
||||||
const [activeKey, setActiveKey] = useState("profile");
|
const [activeKey, setActiveKey] = useState("profile");
|
||||||
const isGroup = "chatroomId" in contract;
|
const isGroup = "chatroomId" in contract;
|
||||||
|
const tabItems = useMemo(() => {
|
||||||
|
const baseItems = [
|
||||||
|
{
|
||||||
|
key: "quickwords",
|
||||||
|
label: "快捷语录",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "profile",
|
||||||
|
label: isGroup ? "群资料" : "个人资料",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (!isGroup) {
|
||||||
|
baseItems.push({
|
||||||
|
key: "moments",
|
||||||
|
label: "朋友圈",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return baseItems;
|
||||||
|
}, [isGroup]);
|
||||||
|
|
||||||
|
const handleTabChange = useCallback((key: string) => {
|
||||||
|
setActiveKey(key);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const tabBarStyle = useMemo(() => ({ padding: "0 30px" }), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sider width={330} className={styles.profileSider}>
|
<Sider width={330} className={styles.profileSider}>
|
||||||
<LayoutFiexd
|
<LayoutFiexd
|
||||||
header={
|
header={
|
||||||
<Tabs
|
<Tabs
|
||||||
activeKey={activeKey}
|
activeKey={activeKey}
|
||||||
onChange={key => setActiveKey(key)}
|
onChange={handleTabChange}
|
||||||
tabBarStyle={{
|
tabBarStyle={tabBarStyle}
|
||||||
padding: "0 30px",
|
items={tabItems}
|
||||||
}}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: "quickwords",
|
|
||||||
label: "快捷语录",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "profile",
|
|
||||||
label: isGroup ? "群资料" : "个人资料",
|
|
||||||
},
|
|
||||||
|
|
||||||
...(!isGroup
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
key: "moments",
|
|
||||||
label: "朋友圈",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{activeKey === "profile" && <ProfileModules contract={contract} />}
|
{activeKey === "profile" && <ProfileModules contract={contract} />}
|
||||||
{activeKey === "quickwords" && (
|
{activeKey === "quickwords" && <QuickWords onInsert={noop} />}
|
||||||
<QuickWords
|
|
||||||
words={[]}
|
|
||||||
onInsert={() => {}}
|
|
||||||
onAdd={() => {}}
|
|
||||||
onRemove={() => {}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{activeKey === "moments" && !isGroup && (
|
{activeKey === "moments" && !isGroup && (
|
||||||
<FriendsCircle wechatFriendId={contract.id} />
|
<FriendsCircle wechatFriendId={contract.id} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,52 @@
|
|||||||
import { createPersistStore } from "@/store/createPersistStore";
|
import { createPersistStore } from "@/store/createPersistStore";
|
||||||
import { Toast } from "antd-mobile";
|
import { Toast } from "antd-mobile";
|
||||||
|
import { databaseManager } from "@/utils/db";
|
||||||
|
|
||||||
|
const STORE_CACHE_KEYS = [
|
||||||
|
"user-store",
|
||||||
|
"app-store",
|
||||||
|
"settings-store",
|
||||||
|
"websocket-store",
|
||||||
|
"ckchat-store",
|
||||||
|
"wechat-storage",
|
||||||
|
"contacts-storage",
|
||||||
|
"message-storage",
|
||||||
|
"customer-storage",
|
||||||
|
];
|
||||||
|
|
||||||
|
const allStorages = (): Storage[] => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const storages: Storage[] = [];
|
||||||
|
try {
|
||||||
|
storages.push(window.localStorage);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("无法访问 localStorage:", error);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
storages.push(window.sessionStorage);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("无法访问 sessionStorage:", error);
|
||||||
|
}
|
||||||
|
return storages;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearStoreCaches = () => {
|
||||||
|
const storages = allStorages();
|
||||||
|
if (!storages.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
STORE_CACHE_KEYS.forEach(key => {
|
||||||
|
storages.forEach(storage => {
|
||||||
|
try {
|
||||||
|
storage.removeItem(key);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`清理持久化数据失败: ${key}`, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -28,7 +75,7 @@ interface UserState {
|
|||||||
setToken: (token: string) => void;
|
setToken: (token: string) => void;
|
||||||
setToken2: (token2: string) => void;
|
setToken2: (token2: string) => void;
|
||||||
clearUser: () => void;
|
clearUser: () => void;
|
||||||
login: (token: string, userInfo: User) => void;
|
login: (token: string, userInfo: User) => Promise<void>;
|
||||||
login2: (token2: string) => void;
|
login2: (token2: string) => void;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
}
|
}
|
||||||
@@ -39,12 +86,27 @@ export const useUserStore = createPersistStore<UserState>(
|
|||||||
token: null,
|
token: null,
|
||||||
token2: null,
|
token2: null,
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
setUser: user => set({ user, isLoggedIn: true }),
|
setUser: user => {
|
||||||
|
set({ user, isLoggedIn: true });
|
||||||
|
databaseManager.ensureDatabase(user.id).catch(error => {
|
||||||
|
console.warn("Failed to initialize database for user:", error);
|
||||||
|
});
|
||||||
|
},
|
||||||
setToken: token => set({ token }),
|
setToken: token => set({ token }),
|
||||||
setToken2: token2 => set({ token2 }),
|
setToken2: token2 => set({ token2 }),
|
||||||
clearUser: () =>
|
clearUser: () => {
|
||||||
set({ user: null, token: null, token2: null, isLoggedIn: false }),
|
databaseManager.closeCurrentDatabase().catch(error => {
|
||||||
login: (token, userInfo) => {
|
console.warn("Failed to close database on clearUser:", error);
|
||||||
|
});
|
||||||
|
clearStoreCaches();
|
||||||
|
set({ user: null, token: null, token2: null, isLoggedIn: false });
|
||||||
|
},
|
||||||
|
login: async (token, userInfo) => {
|
||||||
|
clearStoreCaches();
|
||||||
|
|
||||||
|
// 清除旧的双token缓存
|
||||||
|
localStorage.removeItem("token2");
|
||||||
|
|
||||||
// 只将token存储到localStorage
|
// 只将token存储到localStorage
|
||||||
localStorage.setItem("token", token);
|
localStorage.setItem("token", token);
|
||||||
|
|
||||||
@@ -66,6 +128,11 @@ export const useUserStore = createPersistStore<UserState>(
|
|||||||
lastLoginIp: userInfo.lastLoginIp,
|
lastLoginIp: userInfo.lastLoginIp,
|
||||||
lastLoginTime: userInfo.lastLoginTime,
|
lastLoginTime: userInfo.lastLoginTime,
|
||||||
};
|
};
|
||||||
|
try {
|
||||||
|
await databaseManager.ensureDatabase(user.id);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to initialize user database:", error);
|
||||||
|
}
|
||||||
set({ user, token, isLoggedIn: true });
|
set({ user, token, isLoggedIn: true });
|
||||||
|
|
||||||
Toast.show({ content: "登录成功", position: "top" });
|
Toast.show({ content: "登录成功", position: "top" });
|
||||||
@@ -80,6 +147,10 @@ export const useUserStore = createPersistStore<UserState>(
|
|||||||
// 清除localStorage中的token
|
// 清除localStorage中的token
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
localStorage.removeItem("token2");
|
localStorage.removeItem("token2");
|
||||||
|
databaseManager.closeCurrentDatabase().catch(error => {
|
||||||
|
console.warn("Failed to close user database on logout:", error);
|
||||||
|
});
|
||||||
|
clearStoreCaches();
|
||||||
set({ user: null, token: null, token2: null, isLoggedIn: false });
|
set({ user: null, token: null, token2: null, isLoggedIn: false });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -92,7 +163,11 @@ export const useUserStore = createPersistStore<UserState>(
|
|||||||
isLoggedIn: state.isLoggedIn,
|
isLoggedIn: state.isLoggedIn,
|
||||||
}),
|
}),
|
||||||
onRehydrateStorage: () => state => {
|
onRehydrateStorage: () => state => {
|
||||||
// console.log("User store hydrated:", state);
|
if (state?.user?.id) {
|
||||||
|
databaseManager.ensureDatabase(state.user!.id).catch(error => {
|
||||||
|
console.warn("Failed to restore user database:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createPersistStore } from "@/store/createPersistStore";
|
import { createPersistStore } from "@/store/createPersistStore";
|
||||||
import { Toast } from "antd-mobile";
|
|
||||||
import { useUserStore } from "../user";
|
import { useUserStore } from "../user";
|
||||||
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
|
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
|
||||||
|
import { useCustomerStore } from "@/store/module/weChat/customer";
|
||||||
const { getAccountId } = useCkChatStore.getState();
|
const { getAccountId } = useCkChatStore.getState();
|
||||||
import { msgManageCore } from "./msgManage";
|
import { msgManageCore } from "./msgManage";
|
||||||
// WebSocket消息类型
|
// WebSocket消息类型
|
||||||
@@ -52,6 +52,7 @@ interface WebSocketState {
|
|||||||
reconnectAttempts: number;
|
reconnectAttempts: number;
|
||||||
reconnectTimer: NodeJS.Timeout | null;
|
reconnectTimer: NodeJS.Timeout | null;
|
||||||
aliveStatusTimer: NodeJS.Timeout | null; // 客服用户状态查询定时器
|
aliveStatusTimer: NodeJS.Timeout | null; // 客服用户状态查询定时器
|
||||||
|
aliveStatusUnsubscribe: (() => void) | null;
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
connect: (config: Partial<WebSocketConfig>) => void;
|
connect: (config: Partial<WebSocketConfig>) => void;
|
||||||
@@ -97,6 +98,7 @@ export const useWebSocketStore = createPersistStore<WebSocketState>(
|
|||||||
reconnectAttempts: 0,
|
reconnectAttempts: 0,
|
||||||
reconnectTimer: null,
|
reconnectTimer: null,
|
||||||
aliveStatusTimer: null,
|
aliveStatusTimer: null,
|
||||||
|
aliveStatusUnsubscribe: null,
|
||||||
|
|
||||||
// 连接WebSocket
|
// 连接WebSocket
|
||||||
connect: (config: Partial<WebSocketConfig>) => {
|
connect: (config: Partial<WebSocketConfig>) => {
|
||||||
@@ -405,7 +407,7 @@ export const useWebSocketStore = createPersistStore<WebSocketState>(
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 内部方法:处理连接关闭
|
// 内部方法:处理连接关闭
|
||||||
_handleClose: (event: CloseEvent) => {
|
_handleClose: () => {
|
||||||
const currentState = get();
|
const currentState = get();
|
||||||
|
|
||||||
// console.log("WebSocket连接关闭:", event.code, event.reason);
|
// console.log("WebSocket连接关闭:", event.code, event.reason);
|
||||||
@@ -431,7 +433,7 @@ export const useWebSocketStore = createPersistStore<WebSocketState>(
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 内部方法:处理连接错误
|
// 内部方法:处理连接错误
|
||||||
_handleError: (event: Event) => {
|
_handleError: () => {
|
||||||
// console.error("WebSocket连接错误:", event);
|
// console.error("WebSocket连接错误:", event);
|
||||||
|
|
||||||
set({ status: WebSocketStatus.ERROR });
|
set({ status: WebSocketStatus.ERROR });
|
||||||
@@ -477,42 +479,84 @@ export const useWebSocketStore = createPersistStore<WebSocketState>(
|
|||||||
// 先停止现有定时器
|
// 先停止现有定时器
|
||||||
currentState._stopAliveStatusTimer();
|
currentState._stopAliveStatusTimer();
|
||||||
|
|
||||||
// 获取客服用户列表
|
const requestAliveStatus = () => {
|
||||||
const { kfUserList } = useCkChatStore.getState();
|
const state = get();
|
||||||
|
if (state.status !== WebSocketStatus.CONNECTED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果没有客服用户,不启动定时器
|
const { customerList } = useCustomerStore.getState();
|
||||||
if (!kfUserList || kfUserList.length === 0) {
|
const { kfUserList } = useCkChatStore.getState();
|
||||||
return;
|
const targets =
|
||||||
}
|
customerList && customerList.length > 0
|
||||||
|
? customerList
|
||||||
|
: kfUserList && kfUserList.length > 0
|
||||||
|
? kfUserList
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (targets.length > 0) {
|
||||||
|
state.sendCommand("CmdRequestWechatAccountsAliveStatus", {
|
||||||
|
wechatAccountIds: targets.map(v => v.id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 尝试立即请求一次,如果客服列表尚未加载,后续定时器会继续检查
|
||||||
|
requestAliveStatus();
|
||||||
|
|
||||||
|
const unsubscribeCustomer = useCustomerStore.subscribe(state => {
|
||||||
|
if (
|
||||||
|
get().status === WebSocketStatus.CONNECTED &&
|
||||||
|
state.customerList &&
|
||||||
|
state.customerList.length > 0
|
||||||
|
) {
|
||||||
|
requestAliveStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const unsubscribeKf = useCkChatStore.subscribe(state => {
|
||||||
|
if (
|
||||||
|
get().status === WebSocketStatus.CONNECTED &&
|
||||||
|
state.kfUserList &&
|
||||||
|
state.kfUserList.length > 0
|
||||||
|
) {
|
||||||
|
requestAliveStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 启动定时器,每5秒查询一次
|
// 启动定时器,每5秒查询一次
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
const state = get();
|
const state = get();
|
||||||
// 检查连接状态
|
// 检查连接状态
|
||||||
if (state.status === WebSocketStatus.CONNECTED) {
|
if (state.status === WebSocketStatus.CONNECTED) {
|
||||||
const { kfUserList: currentKfUserList } = useCkChatStore.getState();
|
requestAliveStatus();
|
||||||
if (currentKfUserList && currentKfUserList.length > 0) {
|
|
||||||
state.sendCommand("CmdRequestWechatAccountsAliveStatus", {
|
|
||||||
wechatAccountIds: currentKfUserList.map(v => v.id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 如果连接断开,停止定时器
|
// 如果连接断开,停止定时器
|
||||||
state._stopAliveStatusTimer();
|
state._stopAliveStatusTimer();
|
||||||
}
|
}
|
||||||
}, 5 * 1000);
|
}, 5 * 1000);
|
||||||
|
|
||||||
set({ aliveStatusTimer: timer });
|
set({
|
||||||
|
aliveStatusTimer: timer,
|
||||||
|
aliveStatusUnsubscribe: () => {
|
||||||
|
unsubscribeCustomer();
|
||||||
|
unsubscribeKf();
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 内部方法:停止客服状态查询定时器
|
// 内部方法:停止客服状态查询定时器
|
||||||
_stopAliveStatusTimer: () => {
|
_stopAliveStatusTimer: () => {
|
||||||
const currentState = get();
|
const currentState = get();
|
||||||
|
|
||||||
|
if (currentState.aliveStatusUnsubscribe) {
|
||||||
|
currentState.aliveStatusUnsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
if (currentState.aliveStatusTimer) {
|
if (currentState.aliveStatusTimer) {
|
||||||
clearInterval(currentState.aliveStatusTimer);
|
clearInterval(currentState.aliveStatusTimer);
|
||||||
set({ aliveStatusTimer: null });
|
|
||||||
}
|
}
|
||||||
|
set({ aliveStatusTimer: null, aliveStatusUnsubscribe: null });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ export const PERSIST_KEYS = {
|
|||||||
USER_STORE: "user-store",
|
USER_STORE: "user-store",
|
||||||
APP_STORE: "app-store",
|
APP_STORE: "app-store",
|
||||||
SETTINGS_STORE: "settings-store",
|
SETTINGS_STORE: "settings-store",
|
||||||
|
CKCHAT_STORE: "ckchat-store",
|
||||||
|
WEBSOCKET_STORE: "websocket-store",
|
||||||
|
WECHAT_STORAGE: "wechat-storage",
|
||||||
|
CONTACTS_STORAGE: "contacts-storage",
|
||||||
|
MESSAGE_STORAGE: "message-storage",
|
||||||
|
CUSTOMER_STORAGE: "customer-storage",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// 存储类型
|
// 存储类型
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Dexie, { Table } from "dexie";
|
import Dexie, { Table } from "dexie";
|
||||||
|
import { getPersistedData, PERSIST_KEYS } from "@/store/persistUtils";
|
||||||
|
const DB_NAME_PREFIX = "CunkebaoDatabase";
|
||||||
|
|
||||||
// ==================== 用户登录记录 ====================
|
// ==================== 用户登录记录 ====================
|
||||||
export interface UserLoginRecord {
|
export interface UserLoginRecord {
|
||||||
@@ -123,8 +125,8 @@ class CunkebaoDatabase extends Dexie {
|
|||||||
contactLabelMap!: Table<ContactLabelMap>; // 联系人标签映射表
|
contactLabelMap!: Table<ContactLabelMap>; // 联系人标签映射表
|
||||||
userLoginRecords!: Table<UserLoginRecord>; // 用户登录记录表
|
userLoginRecords!: Table<UserLoginRecord>; // 用户登录记录表
|
||||||
|
|
||||||
constructor() {
|
constructor(dbName: string) {
|
||||||
super("CunkebaoDatabase");
|
super(dbName);
|
||||||
|
|
||||||
// 版本1:统一表结构
|
// 版本1:统一表结构
|
||||||
this.version(1).stores({
|
this.version(1).stores({
|
||||||
@@ -188,12 +190,148 @@ class CunkebaoDatabase extends Dexie {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建数据库实例
|
class DatabaseManager {
|
||||||
export const db = new CunkebaoDatabase();
|
private currentDb: CunkebaoDatabase | null = null;
|
||||||
|
private currentUserId: number | null = null;
|
||||||
|
|
||||||
|
private getDatabaseName(userId: number) {
|
||||||
|
return `${DB_NAME_PREFIX}_${userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async openDatabase(dbName: string) {
|
||||||
|
const instance = new CunkebaoDatabase(dbName);
|
||||||
|
await instance.open();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureDatabase(userId: number) {
|
||||||
|
if (userId === undefined || userId === null) {
|
||||||
|
throw new Error("Invalid userId provided for database initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.currentDb &&
|
||||||
|
this.currentUserId === userId &&
|
||||||
|
this.currentDb.isOpen()
|
||||||
|
) {
|
||||||
|
return this.currentDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.closeCurrentDatabase();
|
||||||
|
|
||||||
|
const dbName = this.getDatabaseName(userId);
|
||||||
|
this.currentDb = await this.openDatabase(dbName);
|
||||||
|
this.currentUserId = userId;
|
||||||
|
|
||||||
|
return this.currentDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentDatabase(): CunkebaoDatabase {
|
||||||
|
if (!this.currentDb) {
|
||||||
|
throw new Error("Database has not been initialized for the current user");
|
||||||
|
}
|
||||||
|
return this.currentDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentUserId() {
|
||||||
|
return this.currentUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInitialized(): boolean {
|
||||||
|
return !!this.currentDb && this.currentDb.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
async closeCurrentDatabase() {
|
||||||
|
if (this.currentDb) {
|
||||||
|
try {
|
||||||
|
this.currentDb.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Failed to close current database:", error);
|
||||||
|
}
|
||||||
|
this.currentDb = null;
|
||||||
|
}
|
||||||
|
this.currentUserId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const databaseManager = new DatabaseManager();
|
||||||
|
|
||||||
|
let pendingDatabaseRestore: Promise<CunkebaoDatabase | null> | null = null;
|
||||||
|
|
||||||
|
async function restoreDatabaseFromPersistedState() {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistedData = getPersistedData<string | Record<string, any>>(
|
||||||
|
PERSIST_KEYS.USER_STORE,
|
||||||
|
"localStorage",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!persistedData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: any = persistedData;
|
||||||
|
|
||||||
|
if (typeof persistedData === "string") {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(persistedData);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Failed to parse persisted user-store value:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = parsed?.state ?? parsed;
|
||||||
|
const userId = state?.user?.id;
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await databaseManager.ensureDatabase(userId);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Failed to initialize database from persisted user:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initializeDatabaseFromPersistedUser() {
|
||||||
|
if (databaseManager.isInitialized()) {
|
||||||
|
return databaseManager.getCurrentDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pendingDatabaseRestore) {
|
||||||
|
pendingDatabaseRestore = restoreDatabaseFromPersistedState().finally(() => {
|
||||||
|
pendingDatabaseRestore = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return pendingDatabaseRestore;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbProxy = new Proxy({} as CunkebaoDatabase, {
|
||||||
|
get(_target, prop: string | symbol) {
|
||||||
|
const currentDb = databaseManager.getCurrentDatabase();
|
||||||
|
const value = (currentDb as any)[prop];
|
||||||
|
if (typeof value === "function") {
|
||||||
|
return value.bind(currentDb);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const db = dbProxy;
|
||||||
|
|
||||||
// 简单的数据库操作类
|
// 简单的数据库操作类
|
||||||
export class DatabaseService<T> {
|
export class DatabaseService<T> {
|
||||||
constructor(private table: Table<T>) {}
|
constructor(private readonly tableAccessor: () => Table<T>) {}
|
||||||
|
|
||||||
|
private get table(): Table<T> {
|
||||||
|
return this.tableAccessor();
|
||||||
|
}
|
||||||
|
|
||||||
// 基础 CRUD 操作 - 使用serverId作为主键
|
// 基础 CRUD 操作 - 使用serverId作为主键
|
||||||
async create(data: Omit<T, "serverId">): Promise<string | number> {
|
async create(data: Omit<T, "serverId">): Promise<string | number> {
|
||||||
@@ -446,10 +584,18 @@ export class DatabaseService<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 创建统一表的服务实例
|
// 创建统一表的服务实例
|
||||||
export const chatSessionService = new DatabaseService(db.chatSessions);
|
export const chatSessionService = new DatabaseService<ChatSession>(
|
||||||
export const contactUnifiedService = new DatabaseService(db.contactsUnified);
|
() => databaseManager.getCurrentDatabase().chatSessions,
|
||||||
export const contactLabelMapService = new DatabaseService(db.contactLabelMap);
|
);
|
||||||
export const userLoginRecordService = new DatabaseService(db.userLoginRecords);
|
export const contactUnifiedService = new DatabaseService<Contact>(
|
||||||
|
() => databaseManager.getCurrentDatabase().contactsUnified,
|
||||||
|
);
|
||||||
|
export const contactLabelMapService = new DatabaseService<ContactLabelMap>(
|
||||||
|
() => databaseManager.getCurrentDatabase().contactLabelMap,
|
||||||
|
);
|
||||||
|
export const userLoginRecordService = new DatabaseService<UserLoginRecord>(
|
||||||
|
() => databaseManager.getCurrentDatabase().userLoginRecords,
|
||||||
|
);
|
||||||
|
|
||||||
// 默认导出数据库实例
|
// 默认导出数据库实例
|
||||||
export default db;
|
export default db;
|
||||||
|
|||||||
Reference in New Issue
Block a user