From 5bdd299dad3ac22fb6dcb8958b1587fea57bc2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B6=85=E7=BA=A7=E8=80=81=E7=99=BD=E5=85=94?= Date: Sat, 30 Aug 2025 15:00:26 +0800 Subject: [PATCH] =?UTF-8?q?refactor(db):=20=E9=87=8D=E6=9E=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E6=9E=B6=E6=9E=84=E4=BD=BF=E7=94=A8serverId?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E4=B8=BB=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将数据库主键从自增id改为直接使用serverId,避免ID冲突 - 简化数据存储和查询逻辑,提高性能 - 添加重复数据检测和去重功能 - 更新相关组件以适配新的数据库接口 - 在应用启动时初始化数据库连接 --- Cunkebao/src/main.tsx | 25 ++- .../ChatWindow/components/Person/index.tsx | 4 +- .../pc/ckbox/components/ChatWindow/index.tsx | 2 +- .../components/VerticalUserList/index.tsx | 21 ++- .../src/store/module/ckchat/ckchat.data.ts | 2 +- Cunkebao/src/store/module/ckchat/ckchat.ts | 27 +-- Cunkebao/src/utils/db.ts | 166 ++++++++++-------- 7 files changed, 152 insertions(+), 95 deletions(-) diff --git a/Cunkebao/src/main.tsx b/Cunkebao/src/main.tsx index 637e8569..49a04054 100644 --- a/Cunkebao/src/main.tsx +++ b/Cunkebao/src/main.tsx @@ -1,10 +1,25 @@ +// main.tsx import React from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; import "./styles/global.scss"; -// 引入错误处理器来抑制findDOMNode警告 -// import VConsole from "vconsole"; -// new VConsole(); +import { db } from "@/utils/db"; // 引入数据库实例 -const root = createRoot(document.getElementById("root")!); -root.render(); +// 数据库初始化 +async function initializeApp() { + try { + // 确保数据库已打开 + await db.open(); + console.log("数据库初始化成功"); + } catch (error) { + console.error("数据库初始化失败:", error); + // 可以选择显示错误提示或使用降级方案 + } + + // 渲染应用 + const root = createRoot(document.getElementById("root")!); + root.render(); +} + +// 启动应用 +initializeApp(); diff --git a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx index 710d399a..177e071b 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/ChatWindow/components/Person/index.tsx @@ -44,7 +44,7 @@ const Person: React.FC = ({ const [isEditingRemark, setIsEditingRemark] = useState(false); const [remarkValue, setRemarkValue] = useState(contract.conRemark || ""); - const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser()); + const getKfSelectedUser = useCkChatStore(state => state.getKfSelectedUser()); // 当contract变化时更新备注值 useEffect(() => { @@ -124,7 +124,7 @@ const Person: React.FC = ({
- {JSON.stringify(kfSelectedUser)} + {JSON.stringify(getKfSelectedUser)} {isEditingRemark ? (
= ({ >({}); const messagesEndRef = useRef(null); - const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser()); + const getKfSelectedUser = useCkChatStore(state => state.getKfSelectedUser()); useEffect(() => { clearUnreadCount([contract.id]).then(() => { setLoading(true); diff --git a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx index dec9d17e..ff4af178 100644 --- a/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx +++ b/Cunkebao/src/pages/pc/ckbox/components/VerticalUserList/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Avatar, Badge, Tooltip } from "antd"; import styles from "./VerticalUserList.module.scss"; import { useCkChatStore, asyncKfSelected } from "@/store/module/ckchat/ckchat"; @@ -14,9 +14,24 @@ const VerticalUserList: React.FC = () => { const handleUserSelect = (userId: number) => { asyncKfSelected(userId); }; - const kfUserList = useCkChatStore(state => state.kfUserList); + const getkfUserList = useCkChatStore(state => state.getkfUserList); const kfSelected = useCkChatStore(state => state.kfSelected); + const [kefuList, setKefuList] = useState([]); + // 获取客服列表数据 + useEffect(() => { + const fetchKfUserList = async () => { + try { + const data = await getkfUserList(); + setKefuList(data || []); + } catch (error) { + console.error("获取客服列表失败:", error); + setKefuList([]); + } + }; + + fetchKfUserList(); + }, [getkfUserList]); return (
{
全部好友
- {kfUserList.map(user => ( + {kefuList.map(user => (
KfUserListData | undefined; + getKfSelectedUser: () => KfUserListData | undefined; newContractList: { groupName: string; contacts: any[] }[]; asyncKfSelected: (data: number) => void; getkfUserList: () => KfUserListData[]; diff --git a/Cunkebao/src/store/module/ckchat/ckchat.ts b/Cunkebao/src/store/module/ckchat/ckchat.ts index 591a2bf1..899bb9f7 100644 --- a/Cunkebao/src/store/module/ckchat/ckchat.ts +++ b/Cunkebao/src/store/module/ckchat/ckchat.ts @@ -6,7 +6,7 @@ import { CkAccount, KfUserListData, } from "@/pages/pc/ckbox/data"; - +import { kfUserService } from "@/utils/db"; export const useCkChatStore = createPersistStore( set => ({ userInfo: null, @@ -16,9 +16,18 @@ export const useCkChatStore = createPersistStore( kfUserList: [], //客服列表 kfSelected: 0, newContractList: [], //联系人分组 - kfSelectedUser: () => { - const state = useCkChatStore.getState(); - return state.kfUserList.find(item => item.id === state.kfSelected); + //客服列表 + asyncKfUserList: async data => { + console.log(data); + + await kfUserService.createManyWithServerId(data); + // set({ kfUserList: data }); + }, + // 获取客服列表 + getkfUserList: async () => { + return await kfUserService.findAll(); + // const state = useCkChatStore.getState(); + // return state.kfUserList; }, asyncKfSelected: (data: number) => { set({ kfSelected: data }); @@ -39,14 +48,12 @@ export const useCkChatStore = createPersistStore( asyncContractList: data => { set({ contractList: data }); }, - // 控制终端用户列表 - getkfUserList: () => { + //获取选中的客服信息 + getgetKfSelectedUser: () => { const state = useCkChatStore.getState(); - return state.kfUserList; - }, - asyncKfUserList: data => { - set({ kfUserList: data }); + return state.kfUserList.find(item => item.id === state.kfSelected); }, + // 删除控制终端用户 deleteCtrlUser: (userId: number) => { set(state => ({ diff --git a/Cunkebao/src/utils/db.ts b/Cunkebao/src/utils/db.ts index 2ec84c0b..943e3b44 100644 --- a/Cunkebao/src/utils/db.ts +++ b/Cunkebao/src/utils/db.ts @@ -1,45 +1,53 @@ /** - * 数据库工具类 - 解决服务器ID与本地自增主键冲突问题 + * 数据库工具类 - 使用serverId作为主键的优化架构 * - * 问题描述: - * 接口返回的数据包含id字段,直接存储到数据库会与Dexie的自增主键(++id)产生冲突 + * 架构设计: + * 1. 使用serverId作为数据库主键,直接对应接口返回的id字段 + * 2. 保留原始的id字段,用于存储接口数据的完整性 + * 3. 简化数据处理逻辑,避免ID映射的复杂性 * - * 解决方案: - * 1. 将服务器返回的id字段映射为serverId字段存储 - * 2. 数据库使用自增的id作为主键 - * 3. 提供专门的方法处理服务器数据的存储和查询 + * 优势: + * - 直接使用服务器ID作为主键,避免ID冲突 + * - 保持数据的一致性和可追溯性 + * - 简化查询逻辑,提高性能 + * - 支持重复数据检测和去重 * * 使用方法: * - 存储接口数据:使用 createWithServerId() 或 createManyWithServerId() - * - 查询服务器数据:使用 findByServerId() - * - 常规操作:使用原有的 create(), findById() 等方法 + * - 查询数据:使用 findById(id) 根据原始ID查询,或 findByPrimaryKey(serverId) 根据主键查询 + * - 批量查询:使用 findByIds([id1, id2, ...]) 根据原始ID批量查询 + * - 内部操作:serverId作为主键用于数据库内部管理 * * 示例: * const serverData = { id: 1001, name: '测试', ... }; // 接口返回的数据 - * const localId = await service.createWithServerId(serverData); // 存储,返回本地ID - * const data = await service.findByServerId(1001); // 根据服务器ID查询 + * const serverId = await service.createWithServerId(serverData); // 存储,返回serverId + * const data = await service.findById(1001); // 根据原始ID查询(用户友好) + * const dataByPK = await service.findByPrimaryKey(serverId); // 根据主键查询(内部使用) */ import Dexie, { Table } from "dexie"; import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data"; -// 扩展数据类型,添加serverId字段 -export interface KfUserWithServerId extends KfUserListData { - serverId?: number | string; // 服务器返回的原始ID +// 数据类型定义,使用serverId作为主键 +export interface KfUserWithServerId extends Omit { + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 } -export interface GroupWithServerId extends GroupData { - serverId?: number | string; // 服务器返回的原始ID +export interface GroupWithServerId extends Omit { + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 } -export interface ContractWithServerId extends ContractData { - serverId?: number | string; // 服务器返回的原始ID +export interface ContractWithServerId extends Omit { + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 } // 新联系人列表数据接口 export interface NewContactListData { - id?: number; - serverId?: number | string; // 服务器返回的原始ID + serverId: number | string; // 服务器ID作为主键 + id?: number; // 接口数据的原始ID字段 groupName: string; contacts: ContractData[] | GroupData[]; } @@ -54,34 +62,16 @@ class CunkebaoDatabase extends Dexie { constructor() { super("CunkebaoDatabase"); - // 版本1:初始版本(不包含serverId字段) + // 版本1:使用serverId作为主键的架构 this.version(1).stores({ kfUsers: - "++id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", + "serverId, id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", groups: - "++id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", + "serverId, id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", contracts: - "++id, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", - newContractList: "++id, groupName, contacts", + "serverId, id, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", + newContractList: "serverId, id, groupName, contacts", }); - - // 版本2:添加serverId字段支持 - this.version(2) - .stores({ - kfUsers: - "++id, serverId, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline", - groups: - "++id, serverId, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar, groupId, config, unreadCount, notice, selfDisplyName", - contracts: - "++id, serverId, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate", - newContractList: "++id, serverId, groupName, contacts", - }) - .upgrade(tx => { - // 数据库升级逻辑:为现有数据添加serverId字段(可选) - console.log("数据库升级到版本2:添加serverId字段支持"); - // 注意:这里不需要迁移数据,因为serverId是可选字段 - // 如果需要迁移现有数据,可以在这里添加相应逻辑 - }); } } @@ -92,64 +82,90 @@ export const db = new CunkebaoDatabase(); export class DatabaseService { constructor(private table: Table) {} - // 基础 CRUD 操作 - async create(data: Omit): Promise { + // 基础 CRUD 操作 - 使用serverId作为主键 + async create(data: Omit): Promise { return await this.table.add(data as T); } - // 创建数据(处理服务器ID映射) - // 用于存储从接口获取的数据,将服务器的id字段映射为serverId,避免与数据库自增主键冲突 - async createWithServerId(data: any): Promise { - const { id, ...restData } = data; + // 创建数据(直接使用接口数据) + // 接口数据的id字段直接作为serverId主键,原id字段保留 + async createWithServerId(data: any): Promise { const dataToInsert = { - ...restData, - serverId: id, // 将服务器的id映射为serverId + ...data, + serverId: data.id, // 使用接口的id作为serverId主键 }; return await this.table.add(dataToInsert as T); } - async findById(id: number): Promise { - return await this.table.get(id); + // 根据原始ID查询(用户友好的查询方法) + async findById(id: string | number): Promise { + return await this.table.where("id").equals(id).first(); + } + + // 根据serverId查询(内部主键查询) + async findByPrimaryKey(serverId: string | number): Promise { + return await this.table.get(serverId); } async findAll(): Promise { return await this.table.toArray(); } - async update(id: number, data: Partial): Promise { - return await this.table.update(id, data as any); + async update(serverId: string | number, data: Partial): Promise { + return await this.table.update(serverId, data as any); } async updateMany( - dataList: { id: number; data: Partial }[], + dataList: { serverId: string | number; data: Partial }[], ): Promise { return await this.table.bulkUpdate( dataList.map(item => ({ - key: item.id, + key: item.serverId, changes: item.data as any, })), ); } - async createMany(dataList: Omit[]): Promise { + async createMany( + dataList: Omit[], + ): Promise<(string | number)[]> { return await this.table.bulkAdd(dataList as T[], { allKeys: true }); } - // 批量创建数据(处理服务器ID映射) - // 用于批量存储从接口获取的数据,将服务器的id字段映射为serverId - async createManyWithServerId(dataList: any[]): Promise { - const processedData = dataList.map(item => { - const { id, ...restData } = item; - return { - ...restData, - serverId: id, // 将服务器的id映射为serverId - }; - }); + // 批量创建数据(直接使用接口数据) + // 接口数据的id字段直接作为serverId主键 + async createManyWithServerId(dataList: any[]): Promise<(string | number)[]> { + // 检查是否存在重复的serverId + const serverIds = dataList.map(item => item.id); + const existingData = await this.table + .where("serverId") + .anyOf(serverIds) + .toArray(); + const existingServerIds = new Set( + existingData.map((item: any) => item.serverId), + ); + + // 过滤掉已存在的数据 + const newData = dataList.filter(item => !existingServerIds.has(item.id)); + + if (newData.length === 0) { + console.log("所有数据都已存在,跳过插入"); + return []; + } + + const processedData = newData.map(item => ({ + ...item, + serverId: item.id, // 使用接口的id作为serverId主键 + })); + + console.log( + `插入 ${processedData.length} 条新数据,跳过 ${dataList.length - newData.length} 条重复数据`, + ); return await this.table.bulkAdd(processedData as T[], { allKeys: true }); } - async delete(id: number): Promise { - await this.table.delete(id); + async delete(serverId: string | number): Promise { + await this.table.delete(serverId); } async clear(): Promise { @@ -164,10 +180,14 @@ export class DatabaseService { .toArray(); } - // 根据服务器ID查询 - // 用于根据原始的服务器ID查找数据 + // 根据服务器ID查询(兼容性方法) async findByServerId(serverId: any): Promise { - return await this.table.where("serverId").equals(serverId).first(); + return await this.table.get(serverId); + } + + // 根据原始ID批量查询 + async findByIds(ids: (string | number)[]): Promise { + return await this.table.where("id").anyOf(ids).toArray(); } // 多值查询(IN 查询)